swift - 无法播放加载了 AVAssetResourceLoaderDelegate 的视频
swift - cannot play a video loaded with AVAssetResourceLoaderDelegate
我正在尝试使用加载到 AVAssetResourceLoaderDelegate 中的 AVPlayer 播放视频,但我总是得到一个空白屏幕并且视频从未播放过。
这是代码:
let asset = AVURLAsset(url: URL(string: "fakescheme://video.mp4")!)
asset.resourceLoader.setDelegate(ResourceLoaderDelegate(), queue: DispatchQueue.main)
let item = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: item)
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer!.frame = self.view.bounds;
self.view.layer.addSublayer(self.playerLayer!)
player!.play()
...
class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource resourceLoadingRequest: AVAssetResourceLoadingRequest) -> Bool {
var newRequest = URLRequest(url: URL(string: "https://example.com/video.mp4")!)
newRequest.allHTTPHeaderFields = resourceLoadingRequest.request.allHTTPHeaderFields
let sessionTask = URLSession.shared.dataTask(with: newRequest) { data, response, error in
resourceLoadingRequest.contentInformationRequest?.contentType = "video/mp4"
resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(data.count)
resourceLoadingRequest.response = response
resourceLoadingRequest.dataRequest?.respond(with: data)
resourceLoadingRequest.finishLoading()
}
sessionTask.resume()
return true
}
}
这是基本思路:
fakescheme:// 导致调用 resourceLoader() 委托,在其中发出新的 HTTP 请求以下载实际视频。
然后它调用 resourceLoadingRequest.dataRequest?.respond(with: data) 应该让 AVPlayer 播放下载的视频。
数据变量已正确填充,但我总是黑屏。
视频文件也很好,如果我直接将它输入 AVURLAsset() 就可以播放。
我试过一百万种东西和组合,但无法使用委托播放。任何帮助将不胜感激!
编辑:我将添加更多信息。
我尝试使用 AES 加密视频执行此操作,它使用 3 个文件 - .m3u8、加密密钥和 .ts 视频。
我设法让它使用委托下载 .m3u8 和密钥,但是当我尝试使用视频文件进行下载时,我再次出现黑屏。
这让我想到可能需要分批下载,但我不确定如何正确地进行下载。
我也找不到任何关于它的文档,例如 - 你是否应该将 HTTP headers 设置为好像它来自 Web 服务器,你是否应该设置 contentInformationRequest 等。委托被调用 2 次:
// first time:
resourceLoadingRequest.dataRequest!.requestedLength == 2,
resourceLoadingRequest.dataRequest!.requestsAllDataToEndOfResource == false
// second time:
resourceLoadingRequest.dataRequest!.requestedLength == MAX_INT,
resourceLoadingRequest.dataRequest!.requestsAllDataToEndOfResource == true
我不知道该怎么做 - 我两次都提供了整个视频,但都没有成功。
我终于让它工作了。所以上面的代码有2个问题:
contentType 应为 AVFileType.mp4.rawValue,即“public.mpeg-4”。传递“video/mp4”或其他值会破坏它
resourceLoadingRequest.dataRequest!.requestedLength确实需要尊重,所以视频文件需要按要求分块发送。
这是工作委托代码:
// this IF is an ugly way to catch the first request to the delegate
// in this request you should populate the contentInformationRequest struct with the size of the video, etc
if (resourceLoadingRequest.dataRequest!.requestedLength == 2) {
let bytes : [UInt8] = [0x0, 0x0] // these are the first 2 bytes of the video, as requested
let data = Data(bytes: bytes, count: bytes.count)
resourceLoadingRequest.contentInformationRequest?.contentType = AVFileType.mp4.rawValue // this is public.mpeg-4, video/mp4 does not work
resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(videoSize)
resourceLoadingRequest.dataRequest!.respond(with: data)
resourceLoadingRequest.finishLoading()
return true
}
// here we are at the second request. the OS may request the entire file, or a portion of it
// here we don't need to set any headers or contentInformationRequest, just reply with the requested data
// take a look at resourceLoadingRequest.dataRequest!.requestedLength, requestedOffset, currentOffset, requestsAllDataToEndOfResource
resourceLoadingRequest.dataRequest?.respond(with: data)
resourceLoadingRequest.finishLoading()
return true
这是整个文件的代码供参考:
import Foundation
import AVKit
class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource resourceLoadingRequest: AVAssetResourceLoadingRequest) -> Bool {
if ((resourceLoadingRequest.request.url?.absoluteString.contains(".mp4"))!) {
// replace the fakeScheme and get the original video url
var originalVideoURLComps = URLComponents(url: resourceLoadingRequest.request.url!, resolvingAgainstBaseURL: false)!
originalVideoURLComps.scheme = "file"
let originalVideoURL = originalVideoURLComps.url
var videoSize = 0
do {
let value = try originalVideoURL!.resourceValues(forKeys: [.fileSizeKey])
videoSize = value.fileSize!
} catch {
print("error getting video size")
}
if (resourceLoadingRequest.contentInformationRequest != nil) {
// this is the first request where we should tell the OS what file is to be downloaded
let bytes : [UInt8] = [0x0, 0x0] // TODO: repeat .requestedLength times?
let data = Data(bytes: bytes, count: bytes.count)
resourceLoadingRequest.contentInformationRequest?.contentType = AVFileType.mp4.rawValue // this is public.mpeg-4, video/mp4 does not work
resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(videoSize)
resourceLoadingRequest.dataRequest!.respond(with: data)
resourceLoadingRequest.finishLoading()
return true
}
// this is the second request where the actual file is to be downloaded
let requestedLength = resourceLoadingRequest.dataRequest!.requestedLength
let requestedOffset = resourceLoadingRequest.dataRequest!.requestedOffset
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: requestedLength)
let inputStream = InputStream(url: originalVideoURL!) // TODO: keep the stream open until a new file is requested?
inputStream!.open()
if (requestedOffset > 0) {
// move the stream pointer to the requested position
let buffer2 = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(requestedOffset))
inputStream!.read(buffer2, maxLength: Int(requestedOffset)) // TODO: the requestedOffset may be int64, but this gets truncated to int!
buffer2.deallocate()
}
inputStream!.read(buffer, maxLength: requestedLength)
// decrypt the video
if (requestedOffset == 0) { // TODO: this == 0 may not always work?
// if you use custom encryption, you can decrypt the video here, buffer[] holds the bytes
}
let data = Data(bytes: buffer, count: requestedLength)
resourceLoadingRequest.dataRequest?.respond(with: data)
resourceLoadingRequest.finishLoading()
buffer.deallocate()
inputStream!.close()
return true
}
return false
}
}
我正在尝试使用加载到 AVAssetResourceLoaderDelegate 中的 AVPlayer 播放视频,但我总是得到一个空白屏幕并且视频从未播放过。
这是代码:
let asset = AVURLAsset(url: URL(string: "fakescheme://video.mp4")!)
asset.resourceLoader.setDelegate(ResourceLoaderDelegate(), queue: DispatchQueue.main)
let item = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: item)
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer!.frame = self.view.bounds;
self.view.layer.addSublayer(self.playerLayer!)
player!.play()
...
class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource resourceLoadingRequest: AVAssetResourceLoadingRequest) -> Bool {
var newRequest = URLRequest(url: URL(string: "https://example.com/video.mp4")!)
newRequest.allHTTPHeaderFields = resourceLoadingRequest.request.allHTTPHeaderFields
let sessionTask = URLSession.shared.dataTask(with: newRequest) { data, response, error in
resourceLoadingRequest.contentInformationRequest?.contentType = "video/mp4"
resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(data.count)
resourceLoadingRequest.response = response
resourceLoadingRequest.dataRequest?.respond(with: data)
resourceLoadingRequest.finishLoading()
}
sessionTask.resume()
return true
}
}
这是基本思路:
fakescheme:// 导致调用 resourceLoader() 委托,在其中发出新的 HTTP 请求以下载实际视频。
然后它调用 resourceLoadingRequest.dataRequest?.respond(with: data) 应该让 AVPlayer 播放下载的视频。 数据变量已正确填充,但我总是黑屏。
视频文件也很好,如果我直接将它输入 AVURLAsset() 就可以播放。
我试过一百万种东西和组合,但无法使用委托播放。任何帮助将不胜感激!
编辑:我将添加更多信息。
我尝试使用 AES 加密视频执行此操作,它使用 3 个文件 - .m3u8、加密密钥和 .ts 视频。 我设法让它使用委托下载 .m3u8 和密钥,但是当我尝试使用视频文件进行下载时,我再次出现黑屏。
这让我想到可能需要分批下载,但我不确定如何正确地进行下载。 我也找不到任何关于它的文档,例如 - 你是否应该将 HTTP headers 设置为好像它来自 Web 服务器,你是否应该设置 contentInformationRequest 等。委托被调用 2 次:
// first time:
resourceLoadingRequest.dataRequest!.requestedLength == 2,
resourceLoadingRequest.dataRequest!.requestsAllDataToEndOfResource == false
// second time:
resourceLoadingRequest.dataRequest!.requestedLength == MAX_INT,
resourceLoadingRequest.dataRequest!.requestsAllDataToEndOfResource == true
我不知道该怎么做 - 我两次都提供了整个视频,但都没有成功。
我终于让它工作了。所以上面的代码有2个问题:
contentType 应为 AVFileType.mp4.rawValue,即“public.mpeg-4”。传递“video/mp4”或其他值会破坏它
resourceLoadingRequest.dataRequest!.requestedLength确实需要尊重,所以视频文件需要按要求分块发送。
这是工作委托代码:
// this IF is an ugly way to catch the first request to the delegate
// in this request you should populate the contentInformationRequest struct with the size of the video, etc
if (resourceLoadingRequest.dataRequest!.requestedLength == 2) {
let bytes : [UInt8] = [0x0, 0x0] // these are the first 2 bytes of the video, as requested
let data = Data(bytes: bytes, count: bytes.count)
resourceLoadingRequest.contentInformationRequest?.contentType = AVFileType.mp4.rawValue // this is public.mpeg-4, video/mp4 does not work
resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(videoSize)
resourceLoadingRequest.dataRequest!.respond(with: data)
resourceLoadingRequest.finishLoading()
return true
}
// here we are at the second request. the OS may request the entire file, or a portion of it
// here we don't need to set any headers or contentInformationRequest, just reply with the requested data
// take a look at resourceLoadingRequest.dataRequest!.requestedLength, requestedOffset, currentOffset, requestsAllDataToEndOfResource
resourceLoadingRequest.dataRequest?.respond(with: data)
resourceLoadingRequest.finishLoading()
return true
这是整个文件的代码供参考:
import Foundation
import AVKit
class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource resourceLoadingRequest: AVAssetResourceLoadingRequest) -> Bool {
if ((resourceLoadingRequest.request.url?.absoluteString.contains(".mp4"))!) {
// replace the fakeScheme and get the original video url
var originalVideoURLComps = URLComponents(url: resourceLoadingRequest.request.url!, resolvingAgainstBaseURL: false)!
originalVideoURLComps.scheme = "file"
let originalVideoURL = originalVideoURLComps.url
var videoSize = 0
do {
let value = try originalVideoURL!.resourceValues(forKeys: [.fileSizeKey])
videoSize = value.fileSize!
} catch {
print("error getting video size")
}
if (resourceLoadingRequest.contentInformationRequest != nil) {
// this is the first request where we should tell the OS what file is to be downloaded
let bytes : [UInt8] = [0x0, 0x0] // TODO: repeat .requestedLength times?
let data = Data(bytes: bytes, count: bytes.count)
resourceLoadingRequest.contentInformationRequest?.contentType = AVFileType.mp4.rawValue // this is public.mpeg-4, video/mp4 does not work
resourceLoadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true
resourceLoadingRequest.contentInformationRequest?.contentLength = Int64(videoSize)
resourceLoadingRequest.dataRequest!.respond(with: data)
resourceLoadingRequest.finishLoading()
return true
}
// this is the second request where the actual file is to be downloaded
let requestedLength = resourceLoadingRequest.dataRequest!.requestedLength
let requestedOffset = resourceLoadingRequest.dataRequest!.requestedOffset
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: requestedLength)
let inputStream = InputStream(url: originalVideoURL!) // TODO: keep the stream open until a new file is requested?
inputStream!.open()
if (requestedOffset > 0) {
// move the stream pointer to the requested position
let buffer2 = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(requestedOffset))
inputStream!.read(buffer2, maxLength: Int(requestedOffset)) // TODO: the requestedOffset may be int64, but this gets truncated to int!
buffer2.deallocate()
}
inputStream!.read(buffer, maxLength: requestedLength)
// decrypt the video
if (requestedOffset == 0) { // TODO: this == 0 may not always work?
// if you use custom encryption, you can decrypt the video here, buffer[] holds the bytes
}
let data = Data(bytes: buffer, count: requestedLength)
resourceLoadingRequest.dataRequest?.respond(with: data)
resourceLoadingRequest.finishLoading()
buffer.deallocate()
inputStream!.close()
return true
}
return false
}
}