iOS列表视频播放优化:播放器池与复用机制实战

发布时间:2026/10/21 13:35:24
iOS列表视频播放优化:播放器池与复用机制实战 简介这是面向iOS中级开发者的列表视频播放功能实现代码包解决在UITableView中嵌入视频流、滚动浏览时自动管理播放状态等典型需求。代码基于AVFoundation框架以AVPlayer与AVPlayerLayer完成视频渲染并在cell复用机制下处理离屏停止与状态重置同时覆盖视频预加载、点击切换播放、全屏展开、手势交互及MVVM状态管理等完整链路可直接参考搭建类似网易视频与今日头条的沉浸式视频列表体验。压缩包共31个文件、约66KB以7个m源码文件与5个h头文件为核心配合xib界面布局、storyboard场景、plist配置及完整Xcode工程文件结构精简便于运行调试与二次开发。目前已有901人学习下载。资源中包含MovieTableViewCell、CustomMoviePlayerView2等核心类并配有视图布局文件开发者可对照理解列表复用下的播放暂停切换、视频预加载时机、性能优化及设备横竖屏适配等关键技术细节。1. 列表视频播放不是套个AVPlayer就完事难点在复用与时机列表视频播放四个字看着简单真做起来很多团队的第一版都会翻车。我见过一个模拟项目X的初版实现直接在列表页的Cell里创建AVPlayer结果滑动时CPU直接飙到60%以上内存冲到200MB最要命的是上下滑动时声音串台——上一个视频还没停下一个已经出了声。这套源码把主流资讯App里的视频流交互拆开重做了一遍播放器池、滑停判定、自动播放条件、缓存与弱网策略还配了一份能跑起来的Demo。适合正在做Feed流视频、短视频列表但不想从零踩坑的iOS开发者。跟着复现一遍你会发现列表视频播放从能播到流畅播之间差的都是些看起来很小的细节。2. 播放器架构设计为什么直接创建AVPlayer会让列表卡到15帧很多人第一次做列表视频播放习惯性拿UITableViewCell跟AVPlayer组合每个Cell在数据源回调里创建播放器把URL塞进去再往AVPlayerLayer里绑定。Demo里这样写没问题一上真机列表就原形毕露。先说结论列表视频播放卡顿的根本原因不是解码性能而是播放器实例过多和生命周期失控。AVPlayer不是轻量对象它内部有渲染输出、音频会话管理、缓冲线程、时间监听器一堆东西。一屏4个Cell加上预加载和复用残留同时存在六七个AVPlayer太常见了CPU和内存直接被拖垮。更麻烦的是Cell复用同一个Cell实例滑出去再滑回来它的播放器还停留在上一个视频的状态不重置就会出现声音重影、画面错位。所以架构上的第一刀就是把播放器从Cell里拎出来由列表页或一个独立的播放器管理器统一持有。Cell只做显示器把自己的AVPlayerLayer绑定到播放器管理器的player上负责展示不负责管理和创建。这个思路确立之后后面所有的优化才有地方落脚。2.1 播放器实例该由谁持有Cell不是播放器的家我在模拟项目X里见过两种最常见的错误写法。第一种是把AVPlayer直接声明成Cell的属性在cellForRowAtIndexPath里初始化这种写法在列表快速滚动时会产生大量播放器实例第二种是用懒加载在Cell里创建播放器复用时不释放旧播放器几十个循环下来内存里躺着十几个AVPlayer谁也不退场。我一般会定义一个播放器管理器负责全列表唯一的播放器实例至少活跃播放器唯一Cell在数据源方法里通过 manager.player 拿到播放器再赋值给AVPlayerLayer。// 播放器管理器列表页持有单个实例Cell只负责画面绑定 interface ListVideoPlayerManager : NSObject property (nonatomic, strong, readonly) AVPlayer *player; // 当前正在播放的视频标识用于校验播放目标是否还是同一个Cell property (nonatomic, copy) NSString *currentVideoId; property (nonatomic, strong) AVPlayerItem *currentItem; (instancetype)sharedManager; // 切换视频先清理旧播放状态再装载新Item - (void)playWithURL:(NSURL *)url videoId:(NSString *)videoId; // 暂停当前播放 - (void)pause; // 释放当前播放资源Cell复用出问题时用到 - (void)reset; end代码逻辑说明playWithURL里面做的事情只有两件——如果currentVideoId和传入userId相同直接return避免重复切换播放源否则调用player replaceCurrentItemWithPlayerItem来换源。cellForRow里只做两件事把manager.player赋给cell的AVPlayerLayer以及把cell.videoId更新成数据模型的videoId。参数说明currentVideoId用字符串而不是NSURL因为列表里同一个URL可能出现在不同位置用ID做身份校验更准确currentItem要单独存一份因为AVPlayer的replaceCurrentItemWithPlayerItem调用后会释放旧Item但你还要保留它的状态来做日志或断点续播。这样改完整个列表同时存活的AVPlayer通常会只剩下一个。内存里那几十个播放器实例没了最明显的变化是滚动时不再持续掉帧Instrument里的Allocations曲线也从锯齿状变成一条平线。2.2 播放器池像复用Cell一样复用播放实例单个播放器的写法最省资源但有一个体验问题从一个视频切到另一个视频时如果路径是播A→停A→创建B→缓冲B→播B中间的停止和缓冲会造成明显的黑屏或转圈。资讯类App的视频流通常要求滑过去就有声有画单播放器做不到。那换个思路做一个播放器池。池里维护2到3个AVPlayer实例一个正在播放一个预加载下一个一个空闲备用。用户滑到新的可见Cell时不需要频繁创建销毁AVPlayer直接从池里取出一个replaceCurrentItem换个源继续播。这跟UITableView复用Cell是同一个哲学。// 播放器池活跃播放器与空闲播放器分开管理 interface PlayerPool : NSObject // 当前正在播放的那个实例 property (nonatomic, strong, readonly) AVPlayer *activePlayer; // 空闲的播放器实例最多3个 property (nonatomic, strong) NSMutableArrayAVPlayer * *idlePlayers; // 从空闲池里取一个出来标记为活跃 - (AVPlayer *)dequeuePlayerWithURL:(NSURL *)url; // 播放器用完后清理状态、放回空闲池 - (void)recyclePlayer:(AVPlayer *)player; end代码逻辑说明dequeuePlayerWithURL先从idlePlayers里拿一个如果空闲池为空就新建一个但总数要封顶超过上限就不能再创建直接复用activePlayer。recyclePlayer里面要做的清理动作包括暂停播放、移除时间观察者、调用replaceCurrentItemWithPlayerItem置空当前Item然后把实例塞回空闲数组。参数说明池大小建议控制在2到3个。开到4个以上预加载带来的内存收益会被抵消在低端机上反而更卡只开1个就退化成单播放器方案黑屏问题又回来了。我一般按屏幕能同时看到几行视频来决定一屏能完整看到1个大图视频就开2个一屏能看到3到4个封面视频开3个。播放器池的核心价值是复用复用意味着代价贵的操作只做一次。AVPlayer创建、音视频会话配置这些动作都不便宜池化之后它们被摊销到整页会话周期里滚动过程中的资源抖动明显变小。就我的使用感受来说同样一段720P视频列表池化播放器之后帧率稳定性比单播放器反复创建高不少。2.3 AVPlayerItem参数配置大多数卡顿的根源在缓冲策略播放器架构定下来之后接着调AVPlayerItem。很多列表视频播放时的卡顿不是解码跟不上而是缓冲策略不合适。默认配置下AVPlayer在弱网环境里会自动等待缓冲这个行为在短视频列表里造成一种很怪的现象滑动过去画面出来了但声音每隔几秒停一下因为系统在等数据。我会在创建Item时统一做这些设置NSURL *videoURL [NSURL URLWithString:item.playUrl]; AVURLAsset *asset [AVURLAsset URLAssetWithURL:videoURL options:{ AVURLAssetPreferPreciseDurationAndTimingKey : YES }]; AVPlayerItem *playerItem [AVPlayerItem playerItemWithAsset:asset]; // 列表播放场景下如果没有足够的缓冲余量就会频繁中断播放 playerItem.preferredForwardBufferDuration 6.0; playerItem.automaticallyWaitsToMinimizeStalling YES;代码逻辑说明preferredForwardBufferDuration是向前预缓冲的时长不是总缓冲时长。设到6.0意味着播放器会尽量预留6秒可播内容这样滑到新视频时能有足够的缓冲余量。automaticallyWaitsToMinimizeStalling设为YES时带宽波动时播放器宁可暂停等待也不会破音或跳帧。参数说明这两个参数有联动关系。缓冲时长设太长比如15秒在列表场景会吃内存一个1080p视频6秒缓冲大概能占到十几MB池里有3个播放器就是几十MB设太短1到2秒遇到弱网立刻卡顿。我常用的经验数值普通资讯流4到6秒高清视频流8到10秒。如果你做的是自动连播的长视频可以放宽到10秒以上但要注意总内存占用。另一个容易忽略的配置是AVURLAsset的optionsAVURLAssetPreferPreciseDurationAndTimingKey设为YES播放器会精确计算时长只在需要精确进度和缩略图时才开。列表播放如果只是循环播、不显示进度条开NO能省一点资源。不过资讯类视频带进度条和拖动预览一般还是保持YES。音频会话也得处理一下AVAudioSession *session [AVAudioSession sharedInstance]; NSError *error nil; [session setCategory:AVAudioSessionCategoryPlayback error:error]; [session setActive:YES error:error];不设置这个真机静音键一拨视频就没声音容易被当成Bug。它虽然跟卡顿无关但我在好几个项目里都遇到测试把这个问题提上来。3. 把资讯App的交互拆成代码自动播放、滑停与进度同步架构层把播放器管住了接下来才是像网易视频和今日头条那样的交互体验滑到就播、停下来才播、滑走了就停。这套交互拆开看其实就三个问题什么时候算停下、播哪一个、以及播放前后画面怎么衔接。3.1 滑停判定不是一停下就播也不是每帧都判断很多新手会把播放判断写在scrollViewDidScroll里每滚动一帧都去遍历Cell、算距离、做播放切换。这样做的后果是滚动期间播放器频繁切换声音一顿一顿的。正确做法是只在两种地方判滚动完全停止或者手指抬起且不再减速。- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self playVisibleVideoIfNeeded]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { [self playVisibleVideoIfNeeded]; } }代码逻辑说明didEndDecelerating是惯性滚动彻底停止后回调didEndDragging里要判断willDecelerate如果是NO说明手指抬起后不继续滚也算一次停止。两个回调都指向同一个播放判断方法避免逻辑写两遍。参数说明这里没有复杂的参数关键在别在didScroll里做判断。滚动过程中播放器处于什么状态我的习惯是滚动开始就暂停播放滚动结束再按可见度决定是否恢复。这样最省资源和电量体验上也符合用户预期——快速滑屏时声音不乱七八糟地响。3.2 播放目标选择可见面积覆盖率与Cell身份校验停止滑动后需要从屏幕上的可见Cell里挑一个来播。选谁不能选第一个也不能选最后一个要选在屏幕上露脸面积最大的那个。比如上一条视频只露出底部一条边下一条完整露出来用户显然是在看下一条。- (void)playVisibleVideoIfNeeded { NSArrayVideoCell * *visibleCells [self.tableView visibleCells]; VideoCell *targetCell nil; CGFloat maxRatio 0.0; for (VideoCell *cell in visibleCells) { CGRect convertedRect [cell convertRect:cell.bounds toView:self.tableView]; CGRect visibleRect CGRectIntersection(convertedRect, self.tableView.bounds); CGFloat ratio CGRectGetHeight(visibleRect) / CGRectGetHeight(convertedRect); if (ratio maxRatio) { maxRatio ratio; targetCell cell; } } if (targetCell maxRatio 0.75) { [self.playerManager playWithURL:targetCell.videoURL videoId:targetCell.videoId]; } }代码逻辑说明第一步遍历所有可见Cell用convertRect把Cell的坐标转换到TableView坐标系再跟TableView的bounds求交集交集高度除以原始高度就是可见占比。最后比值超过0.75才触发播放。参数说明0.75这个阈值是我从多家资讯App的测试结论里得出的经验值——卡片超过四分之三才算用户正在看。阈值设太小比如0.3滑动路过也会触发播放设太大比如0.95上下边缘会出现画面已经露出但半天不出声的尴尬。另外单元格高度固定的列表可以直接算偏移量但混排Feed里Cell高度不一用坐标系求交集更稳。播放器管理器内部还要做一层身份校验- (void)playWithURL:(NSURL *)url videoId:(NSString *)videoId { if ([_currentVideoId isEqualToString:videoId]) { return; // 同一个视频不重复切换播放源 } AVPlayerItem *newItem [AVPlayerItem playerItemWithURL:url]; [_player replaceCurrentItemWithPlayerItem:newItem]; _currentVideoId videoId; _currentItem newItem; [_player play]; }代码逻辑说明如果目标videoId和当前播放的videoId一致直接return防止因为遍历顺序抖动导致播放器反复重置。只有确实换了视频才做replace和play。参数说明videoId必须用服务端下发的唯一标识不要用URL做校验。同一个视频地址可能因为防盗链加签名而每次不同用URL判断会出现同一条视频换个签名又重播一遍的问题。3.3 封面图与首帧切换先出图再出声的细节播放器Item刚替换那一刻画面不会立刻渲染出来。如果这时候直接把封面图移除你会看到黑屏一闪再弹出视频帧。正确的做法是封面图先占着等AVPlayerLayer真正拿到可显示的帧再让封面图淡出。[self.playerLayer addObserver:self forKeyPath:readyForDisplay options:NSKeyValueObservingOptionNew context:nil]; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:readyForDisplay]) { if (self.playerLayer.readyForDisplay) { self.coverImageView.hidden YES; } } }代码逻辑说明AVPlayerLayer有个readyForDisplay属性表示是否已经有视频帧可渲染。监听它而不是监听AVPlayerItem的status是因为item状态变的时机比帧渲染早一步可能status已ready但画面还没出来。参数说明封面图建议用图片库的downsample小图不要在列表里放大图内存。切换封面图的动画时间控制在0.15到0.25秒之间太长会让快速滑动时频繁闪图太短则遮不住黑屏。除了监听readyForDisplay也可以配合AVPlayerItemDidPlayToEndTimeNotification做循环播放处理资讯流里的视频大多不需要循环播完停住、保留最后一帧画面即可。4. 性能调优内存、CPU与网络三个维度的实战记录交互逻辑写完回到老问题列表滑起来涩、内存涨得快、弱网下疯狂转圈。我在模拟项目X上一轮一轮压测把能踩的性能坑基本都趟了一遍。这一章按内存、CPU、网络三个维度记下来参数直接抄。4.1 内存从300MB压到80MB做了四件事先看数据初版列表连续滑30条视频Leaks里看到内存稳定在280MB到300MB。说高不高但iPhone上没别的App占用时这个数值随时会被系统杀掉。降到80MB主要靠四件事。第一控制缓冲堆积。视频播放器默认会不断往下缓冲如果用户只看了6秒就划走后台可能已经缓冲了30秒内容。在滚动停止时判断如果当前不是正在播放的视频立刻把它的播放项停掉具体做法是替换item之前先调用replaceCurrentItemWithPlayerItem置空。- (void)recyclePlayer:(AVPlayer *)player { [player pause]; [player replaceCurrentItemWithPlayerItem:nil]; }第二封面图降采样。列表里的封面图不要用原图图片库加载时指定目标尺寸把解码后的图片大小限制在Cell实际显示尺寸的2倍以内。这一点对内存影响最直接一张2000像素宽的大图比750像素宽的图多占7倍内存。第三及时移除KVO和通知观察者。AVPlayerItem、AVPlayerLayer都支持KVO很多实例泄漏就是Observer只加不删。第四监控播放器数量。用了一个全局的播放器实例计数器每次创建播放器都打点列表页视图WillDisappear时必须清理到0。这么做能快速发现谁偷偷又new了一个AVPlayer。调优项调整前调整后实测效果缓冲时长默认实际可达30秒preferredForwardBufferDuration 6单视频内存降低约30%封面图原图直接展示按显示尺寸2x降采样列表整体内存下降最明显KVO清理页面销毁才统一移除每次切换视频都移除反复滑动后无持续增长播放器数量不限制池化上限3个内存曲线趋于平稳4.2 CPU卡顿离屏渲染和主线程打点都要查CPU卡顿的排查我用两个手段Xcode的Time Profiler真机采样以及Core Animation的Debug选项看离屏渲染。Time Profiler能直接定位到主线程在哪个方法里耗时Debug菜单里勾选Color Offscreen-Rendered会看到所有触发离屏渲染的图层被标记成黄色。列表Cell里最常见的离屏渲染来源就是圆角加阴影我自己就踩过猛更新Cell圆角的坑。把一个封面图的ImageView加上cornerRadius和shadow快速滑动时CPU单帧耗时从4毫秒涨到18毫秒。解决方式很简单把圆角改成图片内容自带或者用图层合并后的截图缓存。另一个CPU大户是播放器的时间监听。addPeriodicTimeObserver的回调频率如果设成每0.1秒一次滚动和播放同时进行时主线程会被打点操作塞满。我在项目里把进度监听降到0.5秒一次UI上的进度条依然看不出延迟。// 播放进度监听列表场景不需要高频刷新调低频率能明显降低主线程占用 __weak typeof(self) weakSelf self; self.timeObserver [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.5, NSEC_PER_SEC) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { // 只在当前Cell可见时才更新进度UI避免后台更新 if (weakSelf.tableView.indexPathsForVisibleRows) { // 更新进度条 } }];代码逻辑说明时间观察者一定要保存引用因为AVPlayer不会帮你自动移除。block里先判断列表是否还有可见行滑出屏幕的Cell就不需要更新进度UI了。参数说明interval的单位是秒0.5秒一个回调足够进度条平滑移动。如果你有逐帧动画或者歌词级联效果那才需要0.1秒甚至更高频率。另外注意观察者要遵循AVPlayer的时间规范CMTimeMakeWithSeconds的第二个参数传NSEC_PER_SEC就行了。4.3 网络预加载队列与弱网降级策略列表视频播放的体验瓶颈最后都集中在网络。滑动流畅了、内存稳了如果视频点开要等3秒转圈用户照样骂。我的做法是两层预加载和弱网降级。预加载的思路是在用户可能滑到下一个视频之前提前创建AVURLAsset并触发一部分数据下载。不需要把整个视频都拉下来只需要让服务器开始响应、提前准备好Range。代码上可以这样触发- (void)preloadNextVideoIfNeeded { NSIndexPath *currentIndex [self currentPlayingIndexPath]; NSIndexPath *nextIndex [NSIndexPath indexPathForRow:currentIndex.row 1 inSection:currentIndex.section]; // 预取下一行视频的元数据和前几KB数据让播放器切换时秒开 AVURLAsset *asset [AVURLAsset URLAssetWithURL:self.dataList[nextIndex.row].playUrl options:nil]; [asset loadValuesAsynchronouslyForKeys:[duration] completionHandler:nil]; }弱网降级则要有明确的判定逻辑。我一般监听AVPlayerItem.status如果状态变成failed先看错误码如果是网络相关错误切一个更低码率的URL再试一次连续失败两次就不再自动重试抛给UI层显示点击重试。同时配合网络状态监听Wi-Fi下自动播放蜂窝网络下只加载封面图让用户手动点击才播放这样既省流量也避免弱网上自动播产生卡顿感。5. 避坑指南列表视频播放最常见的五个翻车现场这一章是我从模拟项目X以及后来帮别人排查过的问题里挑出来的每一个都真实出现过且不翻一次车不会想到是这里的问题。5.1 画面错位同一个Cell显示上一个视频的旧画面现象快速滑动列表后某个Cell上显示的是一张跟当前视频完全无关的画面过1到2秒才被新画面覆盖。原因Cell被复用时AVPlayerLayer还保留着上一个播放器的渲染帧。新视频的Item还没就绪layer不会主动清空旧内容于是旧画面一直挂在那儿。解决在Cell的prepareForReuse里把播放器Layer的显示内容清掉或者重新绑定占位封面图。最简单的处理是给Cell的coverImageView恢复显示等readyForDisplay之后再隐藏封面。- (void)prepareForReuse { [super prepareForReuse]; self.coverImageView.hidden NO; self.videoUrl nil; self.videoId nil; }5.2 多个视频同时出声列表出现声音重叠现象上下滑动列表两个甚至三个视频的声音叠在一起乱成一团。原因在Cell里各自持有播放器滑动时新Cell创建了新播放器旧Cell的播放器没有被暂停或者停止。解决全局只保留一个活跃播放器播放新视频之前必须显式暂停旧的实例。播放器管理器里playWithURL的第一行就调用[self.player pause]保证同一时刻只有一个声音在工作。5.3 滑回已看过的视频黑屏卡顿好几秒现象往上滑到之前已经划过的那条视频黑屏时间比第一次还长。原因播放器池回收时把item置空了但再次使用时重新创建AVPlayerItem又从零开始缓冲。第一次播放可能已经缓冲了60%的数据因为池回收时没有做任何缓存保留第二轮的进度全丢了。解决不要在recycle时无条件清空item。如果视频已经缓冲超过一定比例比如用loadedTimeRanges判断超过50%保留item下次复用同一个item能直接跳过重新缓冲的过程。但要注意保留的item会占用内存池里最多保留1个带缓存的上一个视频。5.4 内存只涨不降Observer反复添加造成泄漏现象页面持续滑动30次内存稳定上涨无论播放器怎么复用Leaks里都能看到AVPlayerItem实例没有被释放。原因给AVPlayer和AVPlayerItem添加KVO后没有在更换item或回收播放器时移除Observer。AVPlayerItem持有Observer的强引用Observer又持有播放器形成环加上底层通知中心的引用实例就无法回收。解决每次切换播放源之前先把旧Item上的观察者全部移除再执行replaceCurrentItem。可以把移除动作统一封装在recyclePlayer方法里。我在后面每次更换播放源都强制走一个cleanup方法这个方法成了播放器的后悔药。5.5 弱网转圈永远不停AVPlayerItem失败后没有重试现象弱网环境下某个视频一直停在loading界面过了一分钟还是不动。原因AVPlayerItem加载失败后status会变成failed播放器不会再次自动尝试。如果没有监听status变化并处理error界面就一直卡在loading状态。解决监听status失败时判断错误域和错误码网络错误就尝试低码率地址重试次数上限设为2次。同时给UI一个超时机制超过8秒还没准备好显示点击重试按钮不跟用户死耗。- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:status]) { AVPlayerItemStatus status [change[NSKeyValueChangeNewKey] integerValue]; if (status AVPlayerItemStatusFailed) { [self handlePlayFailure]; } } }6. 进阶把列表视频播放做到秒开的两个关键技巧前面几章解决的是能正常播和不卡的问题最后一章聊怎么让视频滑到就放。做到秒开主要靠两个技巧首帧预加载和播放器预激活。第一个技巧是封面图到首帧的快速衔接。很多人以为用AVAssetImageGenerator生成一张首帧图显示出来就算秒开但生成缩略图本身要解码而且最终还是要等播放器缓冲。我一般做法是封面图直接使用视频流里的低分辨率首帧由服务端在下发视频地址时顺带一个首帧图地址客户端先显示这张图等到AVPlayerLayer的readyForDisplay变为YES再淡出封面图。监听readyForDisplay的代码前面给过这里不再重复。关键在参数首帧图要针对Cell显示尺寸做压缩我通常限制在800像素宽以内内存占用可以忽略。第二个技巧是播放器预激活。不仅预下载数据连播放器都提前准备好。当检测到用户正在朝某个方向滑动且下一个可见项的索引确定时提前从播放器池取出一个空闲实例把下一个视频的Item替换进去并调用prerollAtRate:completionHandler:让播放器完成渲染前的准备工作。- (void)preparePlayerAtCell:(VideoCell *)cell { if (cell.videoId self.playerManager.currentVideoId) { return; } AVPlayerItem *item [AVPlayerItem playerItemWithURL:cell.videoURL]; AVPlayer *player [self.playerPool dequeuePlayerWithURL:cell.videoURL]; [player replaceCurrentItemWithPlayerItem:item]; [player prerollAtRate:0 completionHandler:nil]; cell.playerLayer.player player; }代码逻辑说明这一步跟正式播放的区别是不调用play只是让播放器完成资源加载和渲染管线准备。等用户真正滑停只需要调用player play就能出声省掉了创建Item、等待ready、设置layer的一长串过程。实测在4G网络下预激活可以让滑停到出声的时间从800毫秒压到200毫秒以内。参数说明prerollAtRate的rate填0表示不改变播放速度。预激活的时机要选好我一般放在scrollViewDidScroll里判断滚动方向后触发但要做节流同一个Cell只预激一次用videoId做去重。注意预激活的播放器不超过2个超出上限会把上一个预激活的实例回收到池里。这套组合做完列表视频播放的体验基本能摸到主流资讯App的边了。从那以后我每次做列表视频播放不管时间多紧都强制自己走一遍这份清单播放器池、滑停判定、跨Cell身份校验、缓冲参数、弱网重试、首帧预加载缺哪一环都不敢上线。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询