I'm trying to make auto-play video in UITableViewCell depending on cell position.
I'm using the AVPlayer
Here is my code:
__weak typeof(self)this = self;
NSString* videoPath = #"http://test.com/test.mp4";
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL URLWithString:videoPath] options:nil];
NSArray* keys = [NSArray arrayWithObjects:#"playable",nil];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^(){
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:asset];
this.avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
this.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:this.avPlayer];
this.avPlayerLayer.frame = _videoContent.frame;
dispatch_async(dispatch_get_main_queue(),^{
[this.videoContent.layer addSublayer:this.avPlayerLayer];
[this.avPlayer play];
});
}];
But my UITableView is frozen when i scroll the table.
I think there are many time-consuming work but most biggest thing is
[this.avPlayer play]
So my question is that AVPlayer is the best way in this situation?
And is there any way to improve the performance?
Are you sure that creating the AVPlayerItem, AVPlayer, and AVPlayerLayer can all be performed off the main thread? You might want to try putting those inside the block that dispatches on the main queue.
Use the below link , this suits for your question.
https://github.com/PRX/PRXPlayer
Related
I have an AVQueuePlayer that is used to play a list of MP3 songs from the internet (http). I need to also know which song is currently playing. The current problem is that loading the song causes a delay that blocks the main thread while waiting for the song to load (first song as well as sequential songs after the first has completed playback).
The following code blocks the main thread:
queuePlayer = [[AVQueuePlayer alloc] init];
[queuePlayer insertItem: [AVPlayerItem playerItemWithURL:url] afterItem: nil]; // etc.
[queuePlayer play]
I am looking for a way to create a playlist of MP3s where the next file to be played back is preloaded in the background.
I tried the following code:
NSArray* tracks = [NSArray arrayWithObjects:#"http://example.com/song1.mp3", #"http://example.com/song2.mp3", #"http://example.com/song3.mp3", nil];
for (NSString* trackName in tracks)
{
AVURLAsset* audioAsset = [[AVURLAsset alloc]initWithURL:[NSURL URLWithString:trackName]
options:nil];
AVMutableCompositionTrack* audioTrack = [_composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
NSError* error;
[audioTrack insertTimeRange:CMTimeRangeMake([_composition duration], audioAsset.duration)
ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0]
atTime:kCMTimeZero
error:&error];
if (error)
{
NSLog(#"%#", [error localizedDescription]);
}
// Store the track IDs as track name -> track ID
[_audioMixTrackIDs setValue:[NSNumber numberWithInteger:audioTrack.trackID]
forKey:trackName];
}
_player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
[_player play];
The issue with this is that I am not sure how to detect when the next song starts playing. Also, the docs don't specify whether or not this will pre-load MP3 files or not.
I am looking for a solution that:
Plays MP3s by pre-loading them in the background prior to playback (ideally start loading the next song before the current song finishes, so it is ready for immediate playback once the current song finishes)
Allow me to view the current song playing.
AVFoundation has some classes designed to do exactly what you're looking for.
It looks like your current solution is to build a single AVPlayerItem that concatenates all of the MP3 files that you want to play. A better solution is to create an AVQueuePlayer with an array of the AVPlayerItem objects that you want to play.
NSArray* tracks = [NSArray arrayWithObjects:#"http://example.com/song1.mp3", #"http://example.com/song2.mp3", #"http://example.com/song3.mp3", nil];
NSMutableArray *playerItems = [[NSMutableArray alloc] init];
for (NSString* trackName in tracks)
{
NSURL *assetURL = [NSURL URLWithString:trackName];
if (!assetURL) {
continue;
}
AVURLAsset* audioAsset = [[AVURLAsset alloc] initWithURL:assetURL
options:nil];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:audioAsset];
[playerItems addObject:playerItem];
}
_player = [[AVQueuePlayer alloc] initWithItems:playerItems];
[_player play];
In answer to your final wrap-up questions:
Yes, AVQueuePlayer DOES preload the next item in the playlist while it's playing the current one.
You can access the currentItem property to determine which AVPlayerItem is currently playing.
I am using AVPLayer to player video in UITableView. Video play properly but sometime initially when video played, sound is coming but screen in black. Video is visible after 5-6 second of video is played. I am using following code:
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
self.avPlayer = avPlayer;
__weak CLBAVPlayer *weakSelf = self;
[self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1.0 / 60.0, NSEC_PER_SEC)
queue:nil
usingBlock:^(CMTime time) {
[weakSelf progress];
}];
self.layer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
self.avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
Please help me to figure out the issue.
I got the answer for this. Actually the issue was that when I was fetching layer and adding it as sublayer then it was not on main queue. Using dispatch_async with the main queue solved my problem.
Looking to add a video to my apps intro screen like the Uber app. I am sure there are others.
I found this which uses a gif and UIWebView, but not sure if this is the best solution. I definitely don't want to use images and stitch them together (would rather not have video if that is the preferred method).
https://medium.com/swift-programming/ios-make-an-awesome-video-background-view-objective-c-swift-318e1d71d0a2
This is what I use in my app Letsplay, The answer from Aslam will work but uses MPmoviePlayerController which is depreciated as of IOS 9.0.
Also I set the video gravity so that your video will fill the entire frame which means no black borders.
NSString *videoPath = [[NSBundle mainBundle] pathForResource:#"icebergs" ofType:#"mp4"];
NSURL *videoURL = [[NSURL alloc] initFileURLWithPath: videoPath];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:[AVURLAsset URLAssetWithURL:videoURL options:nil]];
AVPlayer* videoPlayer = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer* videoPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:videoPlayer];
[videoPlayerLayer setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self.view.layer addSublayer:videoPlayerLayer];
[videoPlayerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[videoPlayer play];
If you don't want sound then remember to mute the player
[videoPlayer setMuted:YES];
I haven't tried the code, but I it should work. It generally takes the path of the video file and adds it to the view controller. You may have to set the coordinates and size. Though I have used here video, GIF is more preferred.
- (void)viewDidAppear:(BOOL)animated
{
[self loadVideoInBackgroundOfView];
}
-(void)loadVideoInBackgroundOfView{
MPMoviePlayerController* videoPlayer = [[MPMoviePlayerController alloc] initWithContentURL:[self getTheVideoPath]];
[videoPlayer.view setFrame:self.view.bounds];
[self.view insertSubview:moviePlayer.view atIndex:0];
[videoPlayer prepareToPlay];
[videoPlayer play];
}
-(NSURL*)getTheVideoPath{
NSString *path = [[NSBundle mainBundle] pathForResource:#"VideoFileName" ofType:#"mp4"];
NSURL *URL = [NSURL fileURLWithPath:path];
return URL;
}
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.
I use AVQueuePlayer to play a sequence of movies which are loaded from URLs.
I tried to initialize player instance with array of all AVPlayerItems that I need to play.
player = [[AVQueuePlayer queuePlayerWithItems:playerItemsArray]
But in this case AVQueuePlayer loads some initial part of each AVPlayerItem before starting playback. It causes frustrating freeze and application doesn't respond for some seconds.
There is possibility to add only first AVPLayerItem to player's queue, observe its state and add second item in queue only when first will reach end, but in this case there will be a gap between playback of two items caused by initializing and buffering of second AVPlayerItem.
Is there any way to organize gapless playback of several videos without a freeze?
Should I use some other player for this purposes?
Thanks in advance.
The solution is found.
When adding new AVPlayerItem in queue of AVQueuePlayer player will synchronously wait till initial part of player item will be buffered.
So in this case player item should be buffered asynchronously and after that it can be added in the queue. It can be done using [AVURLAsset loadValuesAsynchronouslyForKeys: completionHandler:]
For example:
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = [NSArray arrayWithObject:#"playable"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^()
{
dispatch_async(dispatch_get_main_queue(), ^
{
AVPlayerItem *playerItem = [[[AVPlayerItem alloc] initWithAsset:asset] autorelease];
[player insertItem:playerItem afterItem:nil];
});
}];
Using this solution queue of AVQueuePlayer can be populated with items without any gaps and freezes.
in Swift 2, working here:
func load() {
let player = AVQueuePlayer()
for url in urls {
makeItem(url)
}
}
func makeItem(url: String) {
let avAsset = AVURLAsset(URL: NSURL(string: url)!)
avAsset.loadValuesAsynchronouslyForKeys(["playable", "tracks", "duration"], completionHandler: {
dispatch_async(dispatch_get_main_queue(), {
self.enqueue(avAsset: avAsset)
})
})
}
func enqueue(avAsset: AVURLAsset) {
let item = AVPlayerItem(asset: avAsset)
self.player.insertItem(item, afterItem: nil)
}
Here is solution.
- (void)_makePlayer{
_player = [[AVQueuePlayer alloc] initWithPlayerItem:[AVPlayerItem playerItemWithAsset:[SSMoviePreviewItemMaker generateAVMovieItem]]];
}
+ (AVAsset *)generateAVMovieItem{
NSArray * array = [SSMovieFileManager getAllMovieResourceURL];
AVMutableComposition *composition = [[AVMutableComposition alloc] init];
for (int i = 0; i < array.count; i++) {
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:array[i] options:nil];
[composition insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
ofAsset:asset
atTime:composition.duration error:nil];
}
return composition;
}