从 Assets.xcassets 加载视频

Posted

技术标签:

【中文标题】从 Assets.xcassets 加载视频【英文标题】:Load video from Assets.xcassets 【发布时间】:2018-02-13 13:13:44 【问题描述】:

TL;DR:如果我将视频存储在 Assets.xcassets 中,我该如何播放?

我有大约 50 个视频想要存储在应用程序中。然后,我将使用按需资源获取来使我的应用程序大小更轻。但我只能通过将视频保存在 Assets.xcassets 中来实现这一点。而且我找不到从那里加载视频的方法,因为 AVPlayer 似乎只接受 url,而且我不确定如何为这样的本地存储资产获取它。

更新:

所以我做了进一步的挖掘,发现实际上不可能将视频存储在资产目录中并且仍然能够使用它们。我最终不得不将它们从资产目录中移出。至于按需取资源,目录外的资源还是可以的。你可以找到类似的方法here。

【问题讨论】:

【参考方案1】:

现在可以在 Assets.xcassets 中加载视频并且仍然可以播放它们。 在 Xcode 12.0.1 Swift 5 上测试

import UIKit
import AVKit

class ViewController: UIViewController 
    
    override func viewDidLoad() 
        super.viewDidLoad()
    
    
    func createLocalUrl(for filename: String, ofType: String) -> URL? 
        let fileManager = FileManager.default
        let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
        let url = cacheDirectory.appendingPathComponent("\(filename).\(ofType)")
        
        guard fileManager.fileExists(atPath: url.path) else 
            guard let video = NSDataAsset(name: filename)  else  return nil 
            fileManager.createFile(atPath: url.path, contents: video.data, attributes: nil)
            return url
        
        
        return url
    
    
    @IBAction func playLocalVideo(_ sender: Any) 
        guard let videoURL = createLocalUrl(for: "video", ofType: "mp4") else 
            return
        
        
        // Create an AVPlayer, passing it the local video url path
        let player = AVPlayer(url: videoURL as URL)
        let controller = AVPlayerViewController()
        controller.player = player
        present(controller, animated: true) 
            player.play()
        
    

Assets.xcassets image

归功于如何创建本地网址:https://***.com/a/39748919/4267092

【讨论】:

我不确定将这些视频存储在资产文件夹中的好处是否超过需要复制视频才能播放的好处。【参考方案2】:

尝试使用 AVAsset,如 Apple Developer Documentation 中所述。

AVPlayerItem 可以加载 AVAsset 并播放它。加载 AVAssets 文档中的 sn-p:

func prepareToPlay() 
    let url: URL = // Local or Remote Asset URL
    let asset = AVAsset(url: url)

    let assetKeys = [
        "playable",
        "hasProtectedContent"
    ]
    // Create a new AVPlayerItem with the asset and an
    // array of asset keys to be automatically loaded
    playerItem = AVPlayerItem(asset: asset,
                              automaticallyLoadedAssetKeys: assetKeys)

    // Register as an observer of the player item's status property
    playerItem.addObserver(self,
                           forKeyPath: #keyPath(AVPlayerItem.status),
                           options: [.old, .new],
                           context: &playerItemContext)

    // Associate the player item with the player
    player = AVPlayer(playerItem: playerItem)

获取资产的 URL 可能看起来像这样:

let urlpath     = Bundle.main.path(forResource: "myvideo", ofType: "mp4")
let url         = NSURL.fileURL(withPath: urlpath!)

【讨论】:

感谢您的评论。但这实际上并不能解决我的问题。我试图将视频存储在 xcassets 目录中。经过进一步研究,我发现你不能在那里存储视频。我只需要把他们从那里搬走。【参考方案3】:

您可以在资产目录中存储您喜欢的任何数据。但是,您只能将其加载为NSDataAsset,这会给您一个NSData。结果,您做了一些 URL 资源处理程序恶作剧来获取直接从 NSData 读取的 URL。

事情是这样的。

let videoUrl = URL(string: "my-custom-scheme-not-important://\(assetName)")!
let asset = AVURLAsset(url: videoUrl)
guard let dataAsset = NSDataAsset(name: assetName) else 
    fatalError("Data asset not found with name \(assetName)")

let resourceDelegate = DataAssetAVResourceLoader(dataAsset: dataAsset)
assetLoaderDelegate = resourceDelegate
asset.resourceLoader.setDelegate(resourceDelegate, queue: .global(qos: .userInteractive))

let item = AVPlayerItem(asset: asset)

...

@objc
fileprivate class DataAssetAVResourceLoader: NSObject, AVAssetResourceLoaderDelegate 
    let dataAsset: NSDataAsset
    let data: Data
    
    init(dataAsset: NSDataAsset) 
        self.dataAsset = dataAsset
        data = dataAsset.data
    
    
    @objc
    public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool 
        if let infoRequest = loadingRequest.contentInformationRequest 
            infoRequest.contentType = kUTTypeQuickTimeMovie as String
            infoRequest.contentLength = Int64(data.count)
            infoRequest.isByteRangeAccessSupported = true
        
        if let dataRequest = loadingRequest.dataRequest 
            let range = Range(NSRange(location: Int(dataRequest.requestedOffset), length: dataRequest.requestedLength))!
            dataRequest.respond(with: data.subdata(in:range))
            loadingRequest.finishLoading()
            return true
        
        
        return false
    

【讨论】:

【参考方案4】:

根据 Marc Palmer 的回答,我想出了以下解决方案,它有点“更快捷”。

我扩展了NSDataAsset 以符合AVAssetResourceLoaderDelegate 协议,我扩展了AVURLAsset 以使用NSDataAsset 进行初始化。

代码的最后一行使用了一点点 Objective-C 的魔法来保留委托,因为 AVURLAsset 的 ResourceLoader 不这样做,而且 Swift 不允许你在扩展中定义 vars。因为AVURLAsset 是一个Objective-C 类,所以这应该不是问题。 您可以在this post 中阅读有关objc_setAssociatedObject 的更多信息。

    import Foundation
    import AVFoundation

    #if os(macOS)
        import AppKit
    #else
        import UIKit
    #endif

    extension NSDataAsset:AVAssetResourceLoaderDelegate
        @objc public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool 
            
            if let infoRequest = loadingRequest.contentInformationRequest
                infoRequest.contentType = typeIdentifier
                infoRequest.contentLength = Int64(data.count)
                infoRequest.isByteRangeAccessSupported = true
            
            
            if let dataRequest = loadingRequest.dataRequest
                dataRequest.respond(with: data.subdata(in:Int(dataRequest.requestedOffset) ..< Int(dataRequest.requestedOffset) + dataRequest.requestedLength))
                loadingRequest.finishLoading()
                
                return true
            
            return false
        
    
    extension AVURLAsset
        public convenience init?(_ dataAsset:NSDataAsset)
            guard let name = dataAsset.name.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed),
                  let url = URL(string:"NSDataAsset://\(name))")
            else return nil
            
            self.init(url:url) // not really used!
            self.resourceLoader.setDelegate(dataAsset, queue: .main)
            // Retain the weak delegate for the lifetime of AVURLAsset
            objc_setAssociatedObject(self, "AVURLAsset+NSDataAsset", dataAsset, .OBJC_ASSOCIATION_RETAIN)
        

【讨论】:

以上是关于从 Assets.xcassets 加载视频的主要内容,如果未能解决你的问题,请参考以下文章

从 Assets.xcassets 加载图像时 iOS 应用程序崩溃

从 Xcode Swift 中 Assets.xcassets 中的文件夹访问图像

ARKit 图像检测和从 Assets.xcassets 添加图像

Assets.xcassets - 通用不工作

获取位于 Assets.xcassets 的数据集中文件的路径

Cocoapods:包含资源文件夹 Assets.xcassets 的 pod 出现问题