Home > Enterprise >  Avplayer item pre buffering while scrolling on UITableView
Avplayer item pre buffering while scrolling on UITableView

Time:04-30

I have created an UITableView with custom UITableViewCells. UITableView consists of images and videos that load via internet. While user is scrolling in one of the UITableViewCell i load AVPlayer to play a hls video. I set URL in hls format to the avplayer item in order to play the item.

self.playerController = [[AVPlayerViewController alloc] init];
NSURL *videoURL = @"https://playeritemvideourl";
self.player = [AVPlayer playerWithURL:url];
self.playerController.player = self.player;
[self.player play];

The video plays but there is a delay of about 3 to 5 seconds from the moment [self.player play] is triggered. How do i pre buffer the video to the currentitem of avplayer so when i scroll to the specific UITableViewCell the video starts playing instantly? I looked at preferredForwardBufferDuration property on AVPlayerItem but does not seem to make any difference. Any help or suggestions appreciated!

CodePudding user response:

AVPlayer begins streaming m3u8 when it is instantiated. (I noticed this by monitoring the Network graph in the Debug navigator in Xcode. I instantiated an AVPlayer without calling -[play] and the network was under load.) Instead of loading the AVPlayer once the cell becomes visible, instantiate the AVPlayer before the cell is visible and play the content when it becomes visible.

This can be implemented by first having some data structure hold the AVPlayer items. I would recommend a NSMutableDictionary, as we can set the key to the video's URL we want and the object can be the AVPlayer already loaded with the URL. There are two ways to populate the structure. You could load it all at once in a method like -viewDidLoad or load items in dynamically in -scrollViewDidScroll: to determine if we are close to a cell with a video. I would use the former if there is not a lot of content and the user is almost guaranteed to watch all the videos. I would use the former if we have a lot of content to load or if the user might not watch all the videos. Here is an example with a UITableViewController.

MyTableViewController.h

#import <UIKit/UIKit.h>
#import <AVKit/AVKit.h>

@interface MyTableViewController : UITableViewController
{
    NSMutableDictionary *mediaPlayers; // stores the media players, key = NSString with URL, value = AVPlayer
    // you don't need mediaKeyIndexPaths or mediaKeyURLs if you are taking the load-all-at-once approach
    NSMutableSet *mediaKeyIndexPaths; // set that stores the key NSIndexPaths that trigger loading a media player
    NSDictionary *mediaKeyURLs; // dictionary that maps a key NSIndexPath to a URL (NSString), key = NSIndexPath, value = NSString
}

@property (strong, nonatomic) AVPlayerViewController *playerController;

@end

MyTableViewController.m

#import "MyTableViewController.h"

@implementation MyTableViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // if you want to load all items at once, you can forget mediaKeyIndexPaths and mediaKeyURLs
    // and instead just load all of your media here
    mediaPlayers = [[NSMutableDictionary alloc] init];
    
    // if taking the load-all-at-once approach, load mediaPlayers like this
//  NSString *videoURLString = @"https://playeritemvideourl";
//  AVPlayer *player = [AVPlayer playerWithURL:videoURLString];
//  [mediaPlayers setObject:player forKey:@"https://playeritemvideourl"];

    // lets say that the cell with the media in it is defined here
    NSIndexPath *dummyMediaIndexPath = [NSIndexPath indexPathForRow:40 inSection:0];
    // calculate an index path that, when visible, will trigger loading the media at dummyMediaIndexPath
    NSIndexPath *dummyKeyIndexPath = [NSIndexPath indexPathForRow:dummyMediaIndexPath.row-10
                                                  inSection:dummyMediaIndexPath.section];
    
    // add the key index path to the set of key index paths
    mediaKeyIndexPaths = [[NSMutableSet alloc] initWithObjects:dummyKeyIndexPath, nil];
    // define mediaKeyURLs mapping the key dummyKeyIndexPath to the value of the URL string we want
    mediaKeyURLs = [[NSDictionary alloc] initWithObjectsAndKeys:
                    @"https://playeritemvideourl", dummyKeyIndexPath,
                    nil];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 100;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TextCell" forIndexPath:indexPath];
    
    // this is the row of dummyMediaIndexPath defined in -viewDidLoad
    if (indexPath.row == 40)
    {
        self.playerController = [[AVPlayerViewController alloc] init];
        NSString *videoURLString = @"https://playeritemvideourl";
        AVPlayer *player = [mediaPlayers objectForKey:videoURLString]; // load player with URL
        if (!player)
        {
            player = [AVPlayer playerWithURL:[NSURL URLWithString:videoURLString]];
            NSLog(@"Video with URL: %@ was not preloaded. Loading now.", videoURLString);
        }
        self.playerController.player = player;
//      [player play];
// present your playerController here
        [cell setText:@"Player cell"];
    }
    else
    {
        [cell setText:[NSString stringWithFormat:@"Cell #%li", (long)indexPath.row]];
    }
    
    return cell;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    // get visible rows
    NSArray *visibleRows = [self.tableView indexPathsForVisibleRows];
    // traverse through rows
    for (NSIndexPath *i in visibleRows)
    {
        // we use an NSSet to quickly determine if i is contained in mediaKeyIndexPaths
        if ([mediaKeyIndexPaths containsObject:i])
        {
            [mediaKeyIndexPaths removeObject:i]; // we only need to load a player once
            NSString *videoURLString = [mediaKeyURLs objectForKey:i];
            NSLog(@"Preloading URL: %@", videoURLString); // for information purposes only
            AVPlayer *player = [AVPlayer playerWithURL:[NSURL URLWithString:videoURLString]];
            [mediaPlayers setObject:player forKey:videoURLString];
        }
    }
}

@end

This is about as specific as I can make with the amount of code and information you provided. Hope this helps!

  • Related