Retrieving thumbnail from video - Objective C - ios

I have an application that uploads videos into a server and now what I am trying to create is a news feed to display those video post but by a thumbnail or frame of the video. So that once the user clicks the thumbnail the video will play.
I have the following code used to grab the video from the server and play it:
- (void)openVideo {
NSString *videoURLString = #"http://myite.com/dev/iphone/uploads/t69566.mov";
NSURL *videoURL = [NSURL URLWithString:videoURLString];
MPMoviePlayerViewController *moviePlayerView = [[[MPMoviePlayerViewController alloc] initWithContentURL:videoURL] autorelease];
[self presentMoviePlayerViewControllerAnimated:moviePlayerView];
}
Now of coarse I am gong to make openVideo dynamic with the population of video post but I am stuck on taking this video and grabbing a frame or thumbnail and displaying it.
Suggestions? thoughts?
UPDATE:
I want to take a thumbnail from a video taken from server. which actually brings me to a question.
When the user first uploads the video, would it be better to just create a thumbnail there and upload it to my server and just associate that with the video on grabbing them to populate a news feed type?

You can do it using the AVAssetImageGenerator. Rough code, copied and pasted from a working project (but might not be properly isolated):
_thumbnailView is an UIImageView to display the thumbnail.
_activityView is a UIActivityIndicatorView that gets hidden when the thumbnail finishes loading.
AVPlayerItem *playerItem = [[AVPlayerItem playerItemWithURL:movieURL] retain];
AVAssetImageGenerator *_generator;
_generator = [[AVAssetImageGenerator assetImageGeneratorWithAsset:playerItem.asset] retain];
[playerItem release];
AVAssetImageGeneratorCompletionHandler handler = ^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
if (result == AVAssetImageGeneratorSucceeded) {
UIImage *thumb = [UIImage imageWithCGImage:image];
[_thumbnailView setImage:thumb forState:UIControlStateNormal];
} else {
DLog(#"%s - error: %#", __PRETTY_FUNCTION__, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
_thumbnailView.hidden = NO;
_playButton.hidden = NO;
[_activityView stopAnimating];
});
};
[_generator generateCGImagesAsynchronouslyForTimes:[NSArray arrayWithObject:[NSValue valueWithCMTime:CMTimeMakeWithSeconds(30,30)]] completionHandler:handler];
Back to your question, in my opinion, a server side generated thumbnail is usually better, as servers have way more processing power. Also, in order to get this thumbnail you need to start downloading the actual video from the server.
Finally, not that you pass a CMTime parameter including a call to CMTimeMakeWithSeconds(). This might prove problematic, as you can easily hit a blank frame. This is why we are using 30 as the parameter, so we avoid that situation in our videos.

Related

iOS Thumbnail from Amazon S3 MP4/MOV URL

I have several videos uploaded on Amazon S3 bucket. I want to display the videos in a list format in an iOS application along with the thumbnail.
Suppose the url is https://testurlatamzons3/mybucket/somefile.mp4 and I want to get the thumbnail from the URL without actually downloading or streaming the file.
I have seen certain examples and I am able to download the UIImage and display it on the UIImageView. The example I found was here
But for a 350MB file on amazon s3, it takes around 1.9mb of data transfer just to get the thumbnail. Is there a more optimized or a different approach to getting the thumbnails for the mp4/mov files hosted on Amazon S3
Regards,
Nirav
you could get thumbnails using AVFoundation with any time, any size you want:
AVURLAsset *asset=[[AVURLAsset alloc] initWithURL:yourVideoUrl options:nil];
AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generator.appliesPreferredTrackTransform = YES;
CMTime thumbTime = CMTimeMakeWithSeconds(5,30);
AVAssetImageGeneratorCompletionHandler handler = ^(CMTime requestedTime,
CGImageRef im,
CMTime actualTime,
AVAssetImageGeneratorResult result,
NSError *error){
NSLog(#"make sure generator is used in this block and it'll work %#", generator);
};
generator.maximumSize = CGSizeMake(320, 180);
[generator generateCGImagesAsynchronouslyForTimes:[NSArray arrayWithObject:[NSValue valueWithCMTime:thumbTime]] completionHandler:handler];
I guess you have two options:
To have some API which does what you want and returns the URL of the thumbnail image to your application
To download just a small part of the video and grab a frame from it but this depends on the format of your video, maybe this could help for MP4 videos
https://github.com/ohminteractive/OHMTagLib

Getting thumbnail of a video picked from photos library

I know this question has been asked many times on stackoverflow. But my problem is different.
I am iterating on the albumns of photos library to get all videos and their thumbnails.
Now, the problem is, my code is very slow to get the thumbnail of each video. For example, there is 14 videos in my camera roll and the total time taken to generate the thumbnail is around 3-4 seconds. I am using this code.
+(UIImage*)imageFromVideoAtURL:(NSURL*)contentURL {
UIImage* theImage = nil;
AVURLAsset* asset = [[AVURLAsset alloc] initWithURL:contentURL options:nil];
AVAssetImageGenerator* generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generator.appliesPreferredTrackTransform = YES;
NSError* err = NULL;
CMTime time = CMTimeMake(1, 60);
CGImageRef imgRef = [generator copyCGImageAtTime:time actualTime:NULL error:&err];
theImage = [[[UIImage alloc] initWithCGImage:imgRef] autorelease];
CGImageRelease(imgRef);
[asset release];
[generator release];
return theImage;
}
I am finding a way to get the thumbnail of all videos very fast so that user has not to wait. I have seen apps on Apple store that are doing the same thing in just micro seconds. Please help.
I have also tried this code to generate the thumbnail, it is also very slow.
MPMoviePlayerController *moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:mediaUrl];
moviePlayer.shouldAutoplay = NO;
UIImage *thumbnail = [[moviePlayer thumbnailImageAtTime:0.0 timeOption:MPMovieTimeOptionNearestKeyFrame] retain];
[imageView setImage:thumbnail]; //imageView is a UIImageView
If you are loading videos from image library, it should already have the embedded thumbnail of the video. This can be accessed using thumbnail or aspectRatioThumnail methods of ALAsset class.
So in your case the thumbnails could be loaded like:
ALAssetLibrary* lib = [ALAssetLibrary new];
[lib assetForURL:contentURL resultBlock:^(ALAsset* asset) {
CGImageRef thumb = [asset thumbnail];
dispatch_async(dispatch_get_main_queue(), ^{
//do any UI operation here with thumb
});
}];
Please make sure to make any UIKit call in the main queue as assetForURL:: method may invoke the resultBlock in some background thread.

video cover image selector from user dynamically

i am developing video application in that i required video cover image , that i what to select from the user,
select cover image from the user like below image
i search many times in google for custom controller but i didn't get any result, if is there any custom controller ?? or any suggestion for that it is very helpful for me
Actully you can get Video Thumb cover image using path of video file using Bellow code or you can get all frame of video file. But there Apple Not provide any view-controller for choose cover image Thumb for video. you have to create Manually Time at thumbnailImageAtTime or create you own that type of view and get thumb image using Bellow code. Hope that useful.
-(void) getAllImagesFromVideo
{
imagesArray = [[NSMutableArray alloc] initWithCapacity:375];
times = [[NSMutableArray alloc] initWithCapacity:375];
for (Float64 i = 0; i < 15; i += 0.033) // For 25 fps in 15 sec of Video
{
[times addObject:[NSValue valueWithCMTime:CMTimeMakeWithSeconds(i, 60)]];
}
[imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
if (result == AVAssetImageGeneratorSucceeded)
{
[imagesArray addObject:[UIImage imageWithCGImage:image]];
CGImageRelease(image);
}
}];
}
Above code Ref from Get all frames of Video IOS 6 Here you have all frame image of you video. so create you own view-Controller as you mention image in to your question create view-controller like above and set all image in to horizontal scroll-view and select any for your cover image else you can get particular image from time using Bellow code directionally .
-(UIImage *)getThumbNail:(NSString*)stringPath
{
NSURL *videoURL = [NSURL fileURLWithPath:stringPath];
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentURL:videoURL];
UIImage *thumbnail = [player thumbnailImageAtTime:1.0 timeOption:MPMovieTimeOptionNearestKeyFrame];
//Player autoplays audio on init
[player stop];
return thumbnail;
}

How do I preload images from an array of urls in iOS?

I have a music app that I want to preload album arts for. Right now, when you hit next song it loads the album art from a URL but it would be nice if it was already preloaded and in memory. I've tried a few things but nothing seems to really work. Does anyone know of a technique that where I can preload images in iOS and then look for that image later and if its not there, then download it?
Edit with some more detail...
When I go to play a 'station' on my app, I make a request to a server that returns JSON for the playlist. Inside of this JSON is each song as well as data including the album art URL. When a user goes to play a 'station' I would like to preload images for either the next album art URL in the stack or all album art URLs in the entire playlist.
Anyways, I've tried loading the next album art URL in a UIImage in the background as like a temporaryAlbumArt and then when the next song is played simply do albumArt = temporaryAlbumArt and then repeat the process setting the next songs album art to temp and so on..
I've tried something like this:
if (temporaryAlbumArt) {
albumArt = temporaryAlbumArt
} else {
albumArt = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:firstAlbumArtURL]]];
}
temporaryAlbumArt = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nextAlbumArtURL]]];
I guess my question is, what is the best method of:
loading an image or multiple images from a URL(s) into memory
using those images later on without any lag time
You can use this "must have" tool APSmartStorage. It supports everything you need.
This library does what you need: JMImageCache
Loop through your URLs, download them through JMImageCache and when you need to show an image the library will fetch the stored image if it's finished downloading.
//You can do this when you get the array of urls to download the images in the background
for(NSURL *url in urls) {
[[JMImageCache sharedCache] imageForURL:url];
}
//and then when you need an image:
[imageView setImageWithURL:url placeholder:[UIImage imageNamed:#"placeholder.png"]];
I'm not for sure what you are asking but if you know what the next song is going to be couldn't you just down load it in a background thread then have it ready for display when the next song starts?
If you want the app to store all the downloaded images locally you may eventually run into space issues but its do able. You could use the downloaded image then store it in the caches directory (not document dir or Apple will get mad!). When a song comes just do a quick check for the image and if its not local download it. Songs take a few minutes so there should be time to do all this in the background.
#define GET_QUEUE dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
// while song is playing...
dispatch_async(GET_QUEUE, ^{
// On a separate thread look for the image locally in the cache and then
// download it if necessary
});
I'm not sure if this answers your question or not...
Ok so here is a bit more code - basically it lets me ask hopefully helpful questions in context.
// Inside some loop/method that plays songs...
if (temporaryAlbumArt)
albumArt = temporaryAlbumArt
else
albumArt = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:firstAlbumArtURL]]];
// If this keeps executing then perhaps temporaryAlbumArt is getting released somehow?
// Are you persisting it correctly through each iteration/method call?
// show art...
// start music
// Call art update
dispatch_async(GET_QUEUE, ^{
temporaryAlbumArt = nil; // In case the data hasn't been obtained yet on the next iteration???
temporaryAlbumArt = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nextAlbumArtURL]]];
// This works ok then? Can you make sure this is completing correctly before the next iteration/method call?
});
Sorry I can't be of more help.
I have written this code to download data from server and save that data into document directory so that next time if anybody hit the same url,it is fetched from document directory without any server hit.
-(void)downloadImageForProductsFromArray:(NSArray *)inventoryArr {
BOOL isAllDataDownloaded = YES;
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Data Sync...";
[[NSOperationQueue mainQueue] addObserver:self forKeyPath:#"operations" options:0 context:nil];
for (int i = 0; i < inventoryArr.count; i++) {
NSString *productUrlStr = [inventoryArr objectAtIndex:i];
NSData *dirData = [CommonUtils fetchImageDataFromDirectoryWithImageName: productUrlStr];/*****Check whether Image data is already downloaded and saved in document directory*****/
if (!dirData) {
isAllDataDownloaded = NO;
NSBlockOperation *downloadImgOpr = [[NSBlockOperation alloc] init];
downloadImgOpr.queuePriority = NSOperationQueuePriorityVeryHigh;
downloadImgOpr.qualityOfService = NSQualityOfServiceUserInitiated;
[downloadImgOpr addExecutionBlock:^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString: productUrlStr]];
if (imageData) {
/*****Save Image Data in Document Directory*****/
}
}];
}
}
if (isAllDataDownloaded) {
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context{
if (object == [NSOperationQueue mainQueue] && [keyPath isEqualToString:#"operations"]) {
if ([[NSOperationQueue mainQueue].operations count] == 0) {
// Do something here when your queue has completed
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
NSLog(#"queue has completed");
}
}
else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
In This code i have all the url strings in the inventory array.In for loop i have made several operations and added those operations into the queue(I have used main queue for holding UI and syncing data.Choose your queue appropriately).Also I have added the KVO to the queue for keypath value "operations" so that if count of operations is 0,we can do the useful operation.

Load two AVPlayers with one video

I have two different views that are meant to play the same video, I am creating an app that will switch several times between the two views while the video is running.
I currently load the first view with the video as follows:
NSURL *url = [NSURL URLWithString:#"http://[URL TO VIDEO HERE]"];
AVURLAsset *avasset = [[AVURLAsset alloc] initWithURL:url options:nil];
AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:avasset];
player = [[AVPlayer alloc] initWithPlayerItem:item];
playerLayer = [[AVPlayerLayer playerLayerWithPlayer:player] retain];
CGSize size = self.bounds.size;
float x = size.width/2.0-202.0;
float y = size.height/2.0 - 100;
//[player play];
playerLayer.frame = CGRectMake(x, y, 404, 200);
playerLayer.backgroundColor = [UIColor blackColor].CGColor;
[self.layer addSublayer:playerLayer];
NSString *tracksKey = #"tracks";
[avasset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:tracksKey] completionHandler:
^{
dispatch_async(dispatch_get_main_queue(),
^{
NSError *error = nil;
AVKeyValueStatus status = [avasset statusOfValueForKey:tracksKey error:&error];
if (status == AVKeyValueStatusLoaded) {
//videoInitialized = YES;
[player play];
}
else {
// You should deal with the error appropriately.
NSLog(#"The asset's tracks were not loaded:\n%#", [error localizedDescription]);
}
});
}];
In my second view I want to load the video from the dispatch_get_main_queue so that the video in both views are in sync.
I was hoping someone could help me out with loading the data of the video from the first view into the second view.
It is very simple:
Init the first player:
AVAsset *asset = [AVAsset assetWithURL:URL];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
And the second player in the same way, BUT, use the same asset from the first one.
I have verified, it works.
There is all the info you need on the Apple page:
https://developer.apple.com/library/mac/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/02_Playback.html
This abstraction means that you can play a given asset using different
players simultaneously
this quote is from this page.
I don't think you will be able to get this approach to work. Videos are decoded in hardware and then the graphics buffer is sent to the graphics card. What you seem to want to do is decode a video in one view but then capture the contents of the first view and show it in a second view. That will not stay in sync because it would take time to capture the contents of the first window back into main memory and then those contents would need to be sent to the video card again. Basically, that is not going to work. You also cannot decode two h.264 videos streams and expect them to be in sync.
You could implement this with another approach entirely. If you decode the h.264 video to frames on disk (save each frame as a PNG) and then write your own loop that will decode the Nth PNG in a series of PNGs and then display the results in the two different windows. That will work fast enough to be an effective implementation on newer iPhone 4 and 5 and iPad 2 and 3. If you want to make use of a more advanced implementation, take a look at my AVAnimator library for iOS, you could get this approach working in 20 minutes if you use existing code.
For this ten year old question which has only ten year old answers which are out of date, here's the up to date answer.
var leadPlayer: AVPlayer ... the lead player you want to dupe
This does not work:
let leadPlayerItem: AVPlayerItem = leadPlayer.currentItem!
yourPlayer = AVPlayer(playerItem: leadPlayerItem)
yourPlayer.play()
Apple does not allow that (try it, see error).
This works. You must use the item:
let dupeItem: AVPlayerItem = AVPlayerItem(asset: leadPlayer.currentItem!.asset)
yourPlayer = AVPlayer(playerItem: dupeItem)
yourPlayer.play()
Fortunately it's now that easy.

Resources