Is it possible to make AVURLAsset work without a file extension?
Asked Answered
G

4

22

I’m trying to create thumbnails for video files:

- (UIImage*) thumbnailForVideoAtURL: (NSURL*) videoURL
{
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
    AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    CGImageRef imageHandle = [generator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:NULL];
    if (imageHandle) {
        UIImage *frameImage = [UIImage imageWithCGImage:imageHandle];
        CFRelease(imageHandle);
        return frameImage;
    } else {
        return nil;
    }
}

The catch is that the video files are stored in a content-addressable store and have no extensions. This appears to throw AVURLAsset off, as the asset gets created, but upon reading the frame image I get the following error:

Error Domain=AVFoundationErrorDomain Code=-11828 "Cannot Open" UserInfo=0x167170 {NSLocalizedFailureReason=This media format is not supported., NSUnderlyingError=0x163a00 "The operation couldn’t be completed. (OSStatus error -12847.)", NSLocalizedDescription=Cannot Open}

Is this documented or mentioned somewhere? I can’t believe I’m really forced to pass the file format information through the file name. The options argument to the AVURLAsset initializer looks like a good place to supply the filetype, but according to the documentation there does not seem to be any support for that.

PS. I have tested the code, the same file with the correct extension produces the thumbnails just fine.

Gertrudegertrudis answered 15/2, 2012 at 9:41 Comment(0)
G
16

In the end I temporarily create a hardlink to the file and give the correct extension to the hardlink. It’s a hack though, I’d like to see a better solution. I’ve submitted a bug report against AVURLAsset, hopefully Apple could add the feature to read the file format information from the options dictionary.

Gertrudegertrudis answered 19/2, 2012 at 16:15 Comment(10)
Can u tell me plz. that how u created the hard link to the file, actually i have a you tube video url and i have to create the thumbnail of that video using its urlSociability
You mean a local file URL, or a regular HTTP YouTube URL? A local file URL can be easily hardlinked using [NSFileManager linkItemAtURL:toURL:error:]. Creating a thumbnail from an HTTP YouTube video URL is worthy a separate question, IMHO.Gertrudegertrudis
Still not fixed in iOS7 and [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil]; has no problem with files not having extensionsCapers
Hey.. the same approach works in case of PHAsset.. I was unable to read a local file by path fetched from PHAsset. so Linking works –Heliozoan
iOS 10.3 and this is still not fixed. maybe iOS 11? :DTransilluminate
iOS 11 and this is still not fixed :(Quaternity
ios 13 same problem :PMoonscape
ios 14 same problemCilla
iOS 15 same problemHolter
iOS 17 same problemImmigrant
C
9

Swift answer:

let filePath = ###YOU SET THIS###
let fileManager = NSFileManager.defaultManager()
let documentsURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .AllDomainsMask).last!
let tmpURL = documentsURL.URLByAppendingPathComponent("tmp.caf")
_ = try? fileManager.removeItemAtURL(tmpURL)
_ = try? fileManager.linkItemAtURL(fileURL, toURL: tmpURL)

Complete answer Obj-C:

NSString *filePath = ###YOU SET THIS###
NSFileManager *dfm = [NSFileManager defaultManager];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *tmpPath = [[documentPaths lastObject] stringByAppendingPathComponent:@"tmp.caf"];
[dfm removeItemAtPath:tmpPath error:nil];
[dfm linkItemAtPath:filePath toPath:tmpPath error:nil];

And now AVURLAsset will work with tmpPath.

Capers answered 19/10, 2013 at 23:51 Comment(1)
Hey.. the same approach works in case of PHAsset.. I was unable to read a local file by path fetched from PHAsset. so Linking worksHeliozoan
T
7
let mimeType = "video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\""
let asset = AVURLAsset(url: videoURL!, options:["AVURLAssetOutOfBandMIMETypeKey": mimeType])
let playerItem = AVPlayerItem(asset: asset)
avPlayer = AVPlayer(playerItem: playerItem)
self.avPlayerViewConroller?.player = avPlayer
playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions(), context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
        if(avPlayer?.currentItem?.status == AVPlayerItemStatus.readyToPlay)
        {
            avPlayer?.play()
        }
}
Tseng answered 8/1, 2019 at 7:35 Comment(2)
This actually works, at least for AVAssetImageGeneratorBroadminded
Thanks. I couldn't play an audio until I declared mimeType ="audio/mp4". Luckily it doesn't care about the codecs because I can't get codecs of the file even when I use ffprobe.Combustible
L
4

I believe I have found a workaround in iOS7. The trick is to use a AVAssetResourceLoader. Pass into the AVURLAsset a dummy URL which contains a scheme created by you, which will cause the AVAssetResourceLoaderDelegate to be called e.g. 'myscheme://random.com/file.mp4' As you can see with the URL I created it has a .mp4 extension, this will allow the AVPlayer to properly open the file, as long as you follow the next step correctly.

Now implement the AVAssetResourceLoaderDelegate to use the actual URL ie 'http://actual.com/file' The data will now be correctly passed through to the AVPlayer without it complaining about not being able to open that type of file format.

Louella answered 1/11, 2013 at 8:58 Comment(2)
I'm working to implement your recommended workaround, and I'm implementing AVAssetResourceLoaderDelegate, but I'm still unable to get AVPlayer to play the audio source. Can you elaborate on what you meant by "Now implement the AVAssetResourceLoaderDelegate to use the actual URL ie 'actual.com/file'"? Are you swapping out the URLs somehow?Extensity
I did this a while ago, but basically I used a resource scheme with the .mp4 extension so that the delegate would be called and not skipped. Once inside that delegate I used the actual resource address to load it, I used a static method to get at the real resource address instead of using the dummy one which was needed for the delegate to be called correctly.Louella

© 2022 - 2024 — McMap. All rights reserved.