从 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 添加图像