使用后台线程在UICollectionViewCell上加载视频以创建无缝滚动

时间:2017-01-31 23:43:38

标签: ios objective-c multithreading uicollectionview uicollectionviewcell

我正在使用一个Objective-C应用程序,该应用程序具有一个全屏水平滚动UICollectionView的场景。我目前每次在滚动期间在屏幕上出现新单元格时都会调用一个函数,运行大约需要3-4秒,并在新出现的单元格中编辑ui元素。因此,每当新单元格进入屏幕大约4秒钟后,我的应用程序就会滞后,然后继续正常滚动。

有没有办法编辑这个功能并将其放在后台线程上,这样滚动时间不是4秒,而是无缝,不间断的滚动?我知道这个解决方案会导致ui元素出现4秒的延迟,但只要有无缝滚动,我就可以了。

编辑:我认为发布此问题的代码不是一个好主意,因为执行加载的函数中的代码需要我购买的视频播放器API,但我会继续发布下面的方法显示该功能的哪些部分编辑UI。

//variables declared in other parts of the file
KolorEyes *myKolorEyesPlayer;
UICollectionView *collectionViewFollwersFeed;
NSIndexPath *lastPath = nil;

//called repeatedly while scrolling
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{


    if(scrollView==_collectionViewFollwersFeed)
    {

        [self tsLoad]; //function call

    }
}


- (void)tsLoad {

    //calculate index path of cell in the center of the screen
    NSIndexPath *centerCellIndexPath =
    [self.collectionViewFollwersFeed indexPathForItemAtPoint:
     [self.view convertPoint:[self.view center] toView:self.collectionViewFollwersFeed]];

    if(centerCellIndexPath != lastPath) //condition is satisfied if a new cell has been scrolled to the center
    {   
        lastPath = centerCellIndexPath;

        UICollectionViewCell *cell; 
        NSString *CellIdentifier;

        //initialize cell from center path
        CellIdentifier = @"FollowersFeed";
        cell = [_collectionViewFollwersFeed cellForItemAtIndexPath:centerCellIndexPath]; 

        //get respective url for video player
        NSMutableDictionary *dict1=[follwerFeed objectAtIndex:centerCellIndexPath.row];
        NSString *tsstring= [dict1 valueForKey:@"video"];
        NSURL *tsurl = [[NSURL alloc] initWithString:tsstring]; 

        //view that the video will be played in
        UIView *tsView = (UIView *)[cell viewWithTag:99]; 

        //API-specific parameters for video player session
        KolorEyesSessionParams *params = [[KolorEyesSessionParams alloc] init];
        params.delegate = self;
        /* other params properties set like above at this point...
           ...
           ...
        */

        //API-specific initialization
        myKolorEyesPlayer = [[KolorEyes alloc] initWithParams:params];

        //API-specific parameters for video player view
        KolorEyesRenderViewParams *fparams = [[KolorEyesRenderViewParams alloc] init];
        fparams.cameraFieldOfView = 90;
        /* other fparams properties set like above at this point...
           ...
           ...
        */

        //API-specific initializations
        id _tsViewHandle = [myKolorEyesPlayer setRenderView: tsView withParams:fparams];
        [myKolorEyesPlayer setCameraControlMode:kKE_CAMERA_MOTION forView:_tsViewHandle];

        __block KolorEyes *player = myKolorEyesPlayer;

        //error checking
        eKEError err = [myKolorEyesPlayer setAssetURL: tsurl
                                withCompletionHandler:^{
                                    // Media is loaded, play now
                                    [player play];
                                }];

        if (err != kKE_ERR_NONE) {
            NSLog(@"Kolor Eyes setAssetURL failed with error");
        }
    } 
}

4 个答案:

答案 0 :(得分:2)

您可以为运行后台操作定义“ NSOperationQueue ”,当单元格可见时,您可以将视频加载到UICollectionView的单元格。

此外,当不再需要collectionView时,不要忘记利用NSOperationQueue并调用“ cancelAllOperations

答案 1 :(得分:0)

  

有没有办法编辑这个函数并将其放在后台线程上   因此滚动时间不是4秒,而是无缝的,   不间断的滚动?

您正在运行SDK,如上所述:

  

Kolor Eyes是一款免费的360视频播放器应用,可呈现球形视频   具有像素精确投影算法。用户可以选择   通过触摸手势或设备随时可以获得不同的相机视点   运动控制。它不仅仅是一个播放器,而是完美的浏览器设计   适用于Kolor 360视频托管网站。本文档涉及Kolor   从2.0版开始关注iOS

运行苹果自己的足够的问题" AVPlayer"在没有滞后的UICollectionView / UITableView中,你想要运行一个没有任何滞后的Custom made 360​​视频播放器。

由于它是SDK,您无法访问或修改任何代码,因此您需要与SDK的开发人员联系以获取指南。我不认为这个SDK是为了在scrollView中运行而开发的,因为这似乎是一项繁重的任务(但我不确定100%,你需要问开发人员)。

然而,你不应该修改后台线程上的用户界面,因为你要求它不会帮助延迟消失,它会让它变得更糟甚至崩溃或者在大多数情况下挂起你的应用程序,我确信(希望至少这样)开发人员已经使用这个SDK做了尽可能多的线程:

  

在Cocoa应用程序中,主线程运行用户界面,即   是的,所有绘图和所有事件都在主线程上处理。如果你的   应用程序对其执行任何冗长的同步操作   线程,您的用户界面可能会变得无响应并触发   旋转光标。为避免这种情况,您应该缩短时间   这些操作消耗,推迟执行或移动它们   次要线程。

Source

答案 2 :(得分:0)

理论上你是对的。您应该能够在后台线程上进行非UI相关的视频加载(例如下载视频),并在实际想要显示时调度到主线程。在实践中,我发现很多第三方库都很难。大多数视频库都希望完全在主线程上运行。你必须检查你的视频sdk - 但如果他们支持后台加载我会感到惊讶。

另一种解决方案是不在cellForItemAtIndexPath:中加载视频,而是在滚动停止时加载可见单元格的视频。您可以监控代理回调scrollViewDidEndDragging和/或scrollViewDidEndDecelerating

答案 3 :(得分:0)

哦我之前在滚动过程中尝试过播放视频。那时候我没有回复。当你一直在谷歌搜索时,你可能也看到了my question,因为它有很多相同的逻辑?当滚动停止时我最终开始播放视频 - 但那是很久以前的事了,我不再那样了。

现在,我可能会想到你。

在你深入研究之前,我只想说:确实有效,但我不能100%确定它是否适用于视频播放。你必须自己找到它。对不起,我很抱歉,但这可能很复杂。

完全取决于你的情况,细胞数量等,你可能有兴趣禁用重复使用细胞,并简单地预编译所有东西。

之前我没有在UICollectionViewCell完成此操作,我不知道KolorEyes是什么,但这可能适用于UITableViewCellAVPlayer,尽管我没有时间或意志测试它。 我会尝试解释我在想什么的逻辑。

我有一段时间没有做Objective-C所以我会在Swift做这件事,但我希望你明白我在想什么: 首先,制作这个课程:

class PrecompiledCell{
    var cell:UICollectionViewCell? = nil
    var size:CGSize? = nil
    // constructor and such
}

这个类只是你单元格的包装器。现在,您应该为要显示的每个单元格创建一个PrecompiledCell实例,每个关注者都需要一个I.E. 您应该创建一个名为precompileCells()的函数,并在更新dataSource(followerFeed)时调用它。此函数应循环遍历follwerFeed - 数组中的所有元素,并为每个元素创建一个单元格。就像你今天cellForRowAtIndexPath可能正在做的那样。

var followerFeed:[[String:Any]] = [] // Your data. Probably an NSArray with NSDictionaries inside?
var precompiledCells:[PrecompiledCell] = [] //Your array of precompiled cells

func precompileCells(){
    var precompiledCells:[PrecompiledCell] = []
    for feed in followerFeed{
        //Create the cell
        let videoCell = UINib(nibName: "MyVideoCell", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! MyVideoCell
        //Each instance of this  cell (MyVideoCell) should already be initialising its very own KolorEyes-player etc., so now you can say this:
        videoCell.kolorEyesPlayer.setAssetURL(feed["video"]) //Of course, convert String to NSURL etc. first..
        let precompiledCell = PrecompiledCell()
        precompiledCell.cell = videoCell
        precompiledCell.size = //Find out what size the cell is. E.g by using videoCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize), or even easier if you have a constant size.
        precompiledCells.append(precompiledCell)
    }
    self.precompiledCells = precompiledCells
}

现在,两次不做这项工作很重要。这个答案的全部概念是,在使用滚动时,您的常规cellForRowAtIndexPath(或您的情况下为scrollViewDidScroll)不必完成所有这些工作。当前延迟的原因是视图在滚动时初始化了很多东西。使用这些预编译的单元格,在开始滚动之前,所有内容都已初始化。 所以现在,删除正常委托方法的初始化和所有出列,并执行以下操作:

func collectionView(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    return self.precompiledCells[indexPath.row].cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return self.precompiledCells[indexPath.row].size
}

滚动时不再需要初始化内容,因为之前已经初始化了所有内容。你现在唯一需要做的就是使用你的旧函数scrollViewDidScroll,和你做的一样(存储当前索引等),然后简单地放[player play];,而不是其他东西。< / p>

重要的想法

首先,在 collectionView.reloadData()之后调用self.precompileCells 非常重要。所以它应该是这样的:

self.followerFeed = [/*new data*/]
self.precompileCells()
self.collectionView.reloadData()

此外,据我所知,此实现将完全禁用出列,这实际上是不好的做法。如果要装入大量细胞,请不要使用此方法。然后试着把它分开一点。

此实现可能会在实际预编译时导致延迟,而不是在滚动时延迟。

如果以分页方式加载单元格(例如滚动到最后将加载更多单元格),那么您应该真正改进我的代码,因为它将重新预编译所有单元格。然后你应该制作某种insertPrecompiledCells - thingy。

只要您预先编译了单元格,每个单元格都可以自行决定是否应该开始下载视频。如果他们应该下载整个视频,只需几秒钟的视频开始,或者在您要求播放之前不下载它。开始部分下载既可以是异步的,也可以在滚动时更加无缝地启动。

我写这篇文章的另一个想法是,为每个单元格设置KolorEyes - 玩家可能是一个坏主意。您可能希望全局拥有一个KolorEyes - 播放器,只需将其传递到正确的单元格 - 您仍然可以在设置视频播放器之前预编译该单元格,您还可以开始异步下载实际的视频(没有播放器)(取决于KolorEyes播放器到底是什么,但你会想出那个部分......)

同样,这只是一个可能有用的想法。我已经将tableViews用于复杂单元格,以大幅增加滚动的平滑度,但是以非常可控的方式(从不超过30行等)。

我仍然有很多想法和想法,但这篇文章已经太长了,我现在必须走了。祝你好运!