Avassetresourceloaderdelegate Methods Not Working on Device

AVAssetResourceLoaderDelegate methods not working on device

If you take a look on Apple example code where they show bipbop.m3u8 HLS playback you will see that they are using masks for real http requests: "http:/host/bipbop.m3u8" => "custom_scheme:/host/bipbop.m3u8"
Same trick should be made with playlist subresources.

Otherwise avplayer ignores AVAssetResourceLoaderDelegate and load data directly.

You need to implement some kind of mapping:

NSString* videoUrl = @"fake_scheme://host/video.m3u8";
NSURL *streamURL = [NSURL URLWithString:videoUrl];

AVAssetResourceLoaderDelegate and HLS does not work?

Ok, I've found how to fix it, but still don't understand why it's happening only for "m3u8" links.

Need to save strong player or, at least, item reference.
Thanks, Apple, for the docs. :sarcasm:

import UIKit
import AVKit
import AVFoundation

class ViewController: UIViewController, AVAssetResourceLoaderDelegate {
var player: AVPlayer! // <-- the fix

override func viewDidLoad() {
super.viewDidLoad()

// if I change m3u8 to different file extension, it's working good
let url = NSURL(string: "cplp://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8")

let asset = AVURLAsset(URL: url!, options: nil)
asset.resourceLoader.setDelegate(self, queue: dispatch_queue_create("AVARLDelegateDemo loader", nil))

let item = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: item) // <-- the fix
player.play()
}

func resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {

NSLog("This method is never called in case of m3u8 url")

return true
}
}

AVAssetResourceLoader delegate not set

The delegate you associate with this object must adopt the AVAssetResourceLoaderDelegate protocol.
So the code should be like this:

@interface YourClass () <AVAssetResourceLoaderDelegate>
@end

@implementation YourClass

- (void)someMethod
{
NSURL *url = [NSURL URLWithString:@"...."];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
[asset.resourceLoader setDelegate:self queue:queue];
}

#pragma mark - AVAssetResourceLoaderDelegate methods

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{

}

@end

OR

if you have separate delegate MyAssetResourceLoaderDelegateCode somewhere then you have to keep the strong reference to the object MyAssetResourceLoaderDelegateCode you created and pass it as a delegate. When you do [asset.resourceLoader setDelegate:... The resourceLoader is just have a weak reference to the object you made. So, the code can be:

@property (nonatomic, strong) MyAssetResourceLoaderDelegateCode *assetObject;

self.assetObject = [MyAssetResourceLoaderDelegateCode new];

[asset.resourceLoader setDelegate:self.assetObject ...];

swift - cannot play a video loaded with AVAssetResourceLoaderDelegate

I finally got it to work. So there are 2 problems with the above code:

  1. contentType should be AVFileType.mp4.rawValue, which is "public.mpeg-4". Passing "video/mp4" or another value breaks it

  2. resourceLoadingRequest.dataRequest!.requestedLength Indeed needs to be respected, so the video file needs to be sent in chunks as requested.

This is the working delegate code:

// 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

This is the code of the whole file for reference:

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
}
}


Related Topics



Leave a reply



Submit