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

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

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

更新:

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

【问题讨论】:

    标签: ios swift video avplayer


    【解决方案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://stackoverflow.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)
              }
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-10-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-10
        • 1970-01-01
        • 1970-01-01
        • 2021-11-02
        相关资源
        最近更新 更多