I have a UITableView
containing a number of videos to play when scrolling. As the cells in the tableView are being re-used, I only instantiate one AVPlayer
for each row, ever. When a cell is re-used I simply change the PlayerItem
of the cell's player by calling [self.player replaceCurrentItemWithPlayerItem:newItem];
. This is currently being indirectly called inside tableView:cellForRowAtIndexPath
. When scrolling down, there is a noticeable lag when the re-using occurs. With a process of elimination, I have concluded that the lag is caused by replaceCurrentItemWithPlayerItem
, before it's even starting to play. When removing this single line of code (preventing the player from getting a new video) the lagging disappears.
What I've tried to fix it:
I have a custom UITableViewCell
for playing these videos, and I've created a method inside these to initialize with the new information from an object. I.E, in cellForRowAtIndexPath:
i call [cell initializeNewObject:newObject];
to perform the following method:
//In CustomCell.m
-(void)initializeNewObject:(CustomObject*)newObject
{ /*...*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
AVPlayerItem *xPlayerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:newObject.url]];
AVPlayer *dummy = self.player;
[dummy replaceCurrentItemWithPlayerItem:xPlayerItem];
dispatch_async(dispatch_get_main_queue(), ^{
self.player = dummy;
playerItem = xPlayerItem;
}
}/*...*/
}
When running this, I get the same result as if I completely removed the call for replacing the item. Apparently, this function can't be threaded.
I'm not completely sure what I expected from this. I would imagine I need a clean copy
of the AVPlayer
for this to work, but after searching a bit, I found several comments stating that replaceCurrentItemWithPlayerItem:
can not be called in a separate thread, which makes no sense to me. I know that UI-elements should never be handled in other threads than main/UI-thread, but I would never imagine replaceCurrentItemWithPlayerItem
to fall under this category.
I'm now looking for a way to change the item of an AVPlayer
without the lag, but can't find any. I'm hoping I've understood the threading of this function wrong, and that someone would correct me..
EDIT:
I've now been informed that this call is already threaded, and that this shouldn't really happen. However, I see no other explanation.. Below is my cellForRowAtIndexPath:
. It is inside a custom UITableView
with delegates set to self
(so self == tableView
)
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [self dequeueReusableCellWithIdentifier:kCellIdentifier];
if(!cell)
cell = [[[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil] objectAtIndex:0];
//Array 'data' contains all objects to be shown. The objects each have titles, url's etc.
CustomVideoObject *currentObject = [data objectAtIndex:indexPath.row];
//When a cell later starts playing, I store a pointer to the cell in this tableView named 'playing'
//After a quick scroll, the dequeueing cell might be the cell currently playing - resetting
if(playing == cell)
playing = nil;
//This call will insert the correct URL, title, etc for the new video, in the custom cell
[cell initializeNewObject:currentObject];
return cell;
}
The initializeNewObject
that is currently "working", however lagging (this is inside CustomCell.m
:
-(void)initializeNewObject:(CustomObject*)o
{
//If this cell is being dequeued/re-used, its player might still be playing the old file
[self.player pause];
self.currentObject = o;
/*
//Setting text-variables for title etc. Removed from this post, but by commenting them out in the code, nothing improves.
*/
//Replace the old playerItem in the cell's player
NSURL *url = [NSURL URLWithString:self.currentObject.url];
AVAsset *newAsset = [AVAsset assetWithURL:url];
AVPlayerItem *newItem = [AVPlayerItem playerItemWithAsset:newAsset];
[self.player replaceCurrentItemWithPlayerItem:newItem];
//The last line above, replaceCurrentItemWithPlayerItem:, is the 'bad guy'.
//When commenting that one out, all lag is gone (of course, no videos will be playing either)
//The lag still occurs even if I never call [self.player play], which leads me
//to believe that nothing after this function can cause the lag
self.isPlaying = NO;
}
The lag is happening at the exact same place each time. When scrolling fast down we can see that the short lag happens when the top part of the bottom-most cell reaches the center of the screen. I guess this doesn't mean anything to you, but it is clear that the lag is happening at the exact same place every time, namely when a new cell is dequeueing. After changing the playerItem of the AVPlayer
, I do nothing. I do not start to play the video until later. replaceCurrentItemWithPlayerItem:
is causing this noticeable lag. The documentation states that it's changing the item in another thread, but something in that method is holding up my UI.
I was told to use Time Profiler in Instruments to find out what it is, but I have no idea how to do that. By running the profiler, and scrolling once in a while, this is the result(image). The peaks of each graph-group is the lag I am talking about. The one single extreme peak is (I think) when I had scrolled to the bottom and tapped the status bar to scroll to the top. I do not know how to interpret the result. I searched the stack for replace
, and found the bastard. It's called here from within Main Thread
(at the top), which makes sense, with a "running time" of 50ms, which I have no idea to think of. The relevant proportion of the graph is of a timespan of about 1 minute and a half.
In comparison, when commenting out that single line and running Time Profiler again, the graph's peaks are significantly lower (Highest peak at 13%, compared to what's on the image, which is probably around 60-70%).
I don't know what to look for..
If you do some basic level of profiling, I think you can narrow down the problem. For me, I had a similar issue where replaceCurrentItemWithPlayerItem
was blocking the UI thread. I resolved it by examining my code to find out which line was taking time. For me the AVAsset loading was taking time. So I used the loadValuesAsynchronouslyForKeys
method of AVAsset to resolve my issue.
Hence can you try the following:
-(void)initializeNewObject:(CustomObject*)o
{
//If this cell is being dequeued/re-used, its player might still be playing the old file
[self.player pause];
self.currentObject = o;
/*
//Setting text-variables for title etc. Removed from this post, but by commenting them out in the code, nothing improves.
*/
//Replace the old playerItem in the cell's player
NSURL *url = [NSURL URLWithString:self.currentObject.url];
AVAsset *newAsset = [AVAsset assetWithURL:url];
[newAsset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{
AVPlayerItem *newItem = [AVPlayerItem playerItemWithAsset:newAsset];
[self.player replaceCurrentItemWithPlayerItem:newItem];
}];
//The last line above, replaceCurrentItemWithPlayerItem:, is the 'bad guy'.
//When commenting that one out, all lag is gone (of course, no videos will be playing either)
//The lag still occurs even if I never call [self.player play], which leads me
//to believe that nothing after this function can cause the lag
self.isPlaying = NO;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With