iOS视频播放器之ZFPlayer剖析

article/2025/10/3 10:47:48

1、引言


本文主要针对ZFPlayer的功能实现来剖析,以及总结一下大家遇到的问题和解决方案 首先ZFPlayer现在拥有的功能:

  • 支持横、竖屏切换,在全屏播放模式下还可以锁定屏幕方向

  • 支持本地视频、网络视频播放

  • 支持在TableviewCell播放视频

  • 左侧1/2位置上下滑动调节屏幕亮度(模拟器调不了亮度,请在真机调试)

  • 右侧1/2位置上下滑动调节音量(模拟器调不了音量,请在真机调试)

  • 左右滑动调节播放进度

  • 全屏状态下拖动slider控制进度,显示视频的预览图

  • 断点下载功能

  • 切换视频分辨率

ZFPlayer是对AVPlayer的封装,有人会问它支持什么格式的视频播放,问这个问题的可以自行搜索AVPlayer支持的格式。

跟AVPlayer联系密切的名词:

  • Asset:AVAsset是抽象类,不能直接使用,其子类AVURLAsset可以根据URL生成包含媒体信息的Asset对象。

  • AVPlayerItem:和媒体资源存在对应关系,管理媒体资源的信息和状态。

  • AVPlayerLayer: CALayer的subclass,它主要用来在iOS中播放视频内容

2、具体功能实现


2.1 通过一个网络链接播放视频

AVURLAsset *urlAsset = [AVURLAsset assetWithURL:videoURL];

// 初始化playerItem

AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:urlAsset];

// 也可以使用来初始化playerItem

// AVPlayerItem * playerItem = [AVPlayerItem playerItemWithURL:videoURL];

// 初始化Player

AVPlayer *player = [AVPlayer playerWithPlayerItem:self.playerItem];

// 初始化playerLayer

AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];

// 添加playerLayer到self.layer

[self.layer insertSublayer:self.playerLayer atIndex:0];

2.2 播放器的常用操作

  • 播放:

[player play];

需要注意的是初始化完player之后不一定会马上开始播放,需要等待player的状态变为ReadyToPlay才会进行播放。

  • 暂停:

[player pause];

2.3 播放多个items

这里我们有两种方式可以实现,一种是由你自行控制下一首歌曲的item,将其替换到当前播放的item

[player replaceCurrentItemWithPlayerItem:playerItem];

在iOS9后,AVPlayer的replaceCurrentItemWithPlayerItem方法在切换视频时底层会调用信号量等待然后导致当前线程卡顿,如果在UITableViewCell中切换视频播放使用这个方法,会导致当前线程冻结几秒钟。遇到这个坑还真不好在系统层面对它做什么,后来找到的解决方法是在每次需要切换视频时,需重新创建AVPlayer和AVPlayerItem。

另一种可以使用AVQueuePlayer播放多个items,AVQueuePlayer是AVPlayer的子类,可以用一个数组来初始化一个AVQueuePlayer对象。代码如下:

NSArray *items = <#An array of player items#>;

AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];

和AVPlayer一样,直接调用play方法来播放,queue player顺序播放队列中的item,如果想要跳过一个item,播放下一个item,可以调用方法advanceToNextItem。

可以对队列进行插入和删除操作,调用方法insertItem:afterItem:, removeItem:, 和 removeAllItems。正常情况下当插入一个item之前,应该检查是否可以插入,通过使用canInsertItem:afterItem:方法,第二个参数传nil,代码如下:

AVPlayerItem *anItem = <#Get a player item#>;

if ([queuePlayer canInsertItem:anItem afterItem:nil]) {

[queuePlayer insertItem:anItem afterItem:nil];

}

2.4 seekToTime指定从某一秒开始播放

可以使用seekToTime:定位播放头到指定的时间,如下代码:

CMTime fiveSecondsIn = CMTimeMake(5, 1);

[player seekToTime:fiveSecondsIn];

seekTime:不能精确定位,如果需要精确定位,可以使用seekToTie:toleranceBefore:toleranceAfter:,代码如下:

CMTime fiveSecondsIn = CMTimeMake(5, 1);

[player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];

当tolerance=0的时候,framework需要进行大量解码工作,比较耗性能,所以,只有当你必须使用的时候才用这个方法,比如开发一个复杂的多媒体编辑应用,这需要精确的控制。

关于重播什么的就不用我多说了吧,点击重播seekToTime:kCMTimeZero。还有关于下次播放的时候从上次离开的那个时间开始播放,大家都有思路啦吧,当离开当前视频时候记录播放到哪一秒了,下次点开直接seekToTime到那一秒开始播放就好了嘛。

2.5 监听播放进度

使用addPeriodicTimeObserverForInterval:queue:usingBlock:来监听播放器的进度 (1)方法传入一个CMTime结构体,每到一定时间都会回调一次,包括开始和结束播放 (2)如果block里面的操作耗时太长,下次不一定会收到回调,所以尽量减少block的操作耗时 (3)方法会返回一个观察者对象,当播放完毕时需要移除这个观察者 添加观察者:

id timeObserve = [player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

float current = CMTimeGetSeconds(time);

float total = CMTimeGetSeconds(songItem.duration);

if (current) {

weakSelf.progress = current / total;

weakSelf.playTime = [NSString stringWithFormat:@"%.f",current];

weakSelf.playDuration = [NSString stringWithFormat:@"%.2f",total];

}

}];

移除观察者:

if (timeObserve) {

[player removeTimeObserver:_timeObserve];

timeObserve = nil;

}

2.6 监听改播放器状态

[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

播放器的三种状态,当playerItem的状态变为AVPlayerItemStatusReadyToPlay才会进行播放。

typedef NS_ENUM(NSInteger, AVPlayerItemStatus) {

AVPlayerItemStatusUnknown,

AVPlayerItemStatusReadyToPlay,

AVPlayerItemStatusFailed

};

播放完了需要移除观察者

[playerItem removeObserver:self forKeyPath:@"status"];

2.7 监听缓冲进度

[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

播放完了需要移除观察者

[playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];

2.8 监听网络缓冲状态

// 缓冲区空了,需要等待数据

[playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];

// 缓冲区有足够数据可以播放了

[playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];

播放完了需要移除观察者

[playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];

[playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];

2.9 监听AVPlayer播放完成通知

监听通知AVPlayerItemDidPlayToEndTimeNotification,来处理一些播放完的事情

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

2.10 系统音量相关

/**

* 获取系统音量

*/

- (void)configureVolume

{

MPVolumeView *volumeView = [[MPVolumeView alloc] init];

_volumeViewSlider = nil;

for (UIView *view in [volumeView subviews]){

if ([view.class.description isEqualToString:@"MPVolumeSlider"]){

_volumeViewSlider = (UISlider *)view;

break;

}

}

// 使用这个category的应用不会随着手机静音键打开而静音,可在手机静音下播放声音

NSError *setCategoryError = nil;

BOOL success = [[AVAudioSession sharedInstance]

setCategory: AVAudioSessionCategoryPlayback

error: &setCategoryError];

if (!success) { /* handle the error in setCategoryError */ }

// 监听耳机插入和拔掉通知

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];

}

/**

* 耳机插入、拔出事件

*/

- (void)audioRouteChangeListenerCallback:(NSNotification*)notification

{

NSDictionary *interuptionDict = notification.userInfo;

NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];

switch (routeChangeReason) {

case AVAudioSessionRouteChangeReasonNewDeviceAvailable:

// 耳机插入

break;

case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:

{

// 耳机拔掉

// 拔掉耳机继续播放

[self play];

}

break;

case AVAudioSessionRouteChangeReasonCategoryChange:

// called at start - also when other audio wants to play

NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");

break;

}

}

设置系统音量

// 0 ... 1.0的数值, 1.0是最大的声音.

self.volumeViewSlider.value = ...

2.11 屏幕亮度相关

// 0 ... 1.0的数值, 1.0是最大的亮度.

[UIScreen mainScreen].brightness = ...

2.12 屏幕旋转相关

苹果手机除iPhone 4s(320*480)屏幕宽高比不是16:9外,其他都为16:9,所以横竖屏可以这样实现,这里必须使用autolayout,这里提供两种方法实现:

  • 使用Xib或者Storyboard的话,必须把播放器view的宽高比设置成16:9,4s的话可以单独适配加约束(使用sizeClasses)

  • 使用masonry,具体代码如下:

[self.playerView mas_makeConstraints:^(MASConstraintMaker *make) {

make.top.equalTo(self.view).offset(20);

make.left.right.equalTo(self.view);

// 注意此处,宽高比16:9优先级比1000低就行,在因为iPhone 4S宽高比不是16:9

make.height.equalTo(self.playerView.mas_width).multipliedBy(9.0f/16.0f).with.priority(750);

}];

关于屏幕旋转可以这样强制让屏幕转屏,有人会问了,在我demo中为啥能转屏,而集成到自己项目中不能转屏,我可以明确的告诉你,是你们项目的横屏给禁止掉了,你可以看一下这里是否打钩啦:

设备方向

有人又会问了,我们想实现这么个需求,只有在播放器页面支持横屏,其他页面不支持横屏。好了,那下边我来告诉怎么实现,首先上图中的横屏必须勾选,其次在你需要转屏的ViewController中来实现三个方法:

// 是否支持自动转屏

- (BOOL)shouldAutorotate

{

// 调用ZFPlayerSingleton单例记录播放状态是否锁定屏幕方向

return !ZFPlayerShared.isLockScreen;

}

// 支持哪些转屏方向

- (UIInterfaceOrientationMask)supportedInterfaceOrientations

{

return UIInterfaceOrientationMaskAllButUpsideDown;

}

// 页面展示的时候默认屏幕方向(当前ViewController必须是通过模态ViewController(模态带导航的无效)方式展现出来的,才会调用这个方法)

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation

{

return UIInterfaceOrientationPortrait;

}

ZFPlayer内部已经实现屏幕旋转的分类(UITabBarController+ZFPlayerRotation.h UINavigationController+ZFPlayerRotation UIViewController+ZFPlayerRotation),不管你项目的rootViewController的是UINavigationController还是UITabBarController,则只需要在支持除竖屏以外的控制器实现上边三个方法就行。

下边来说说强制屏幕旋转,即使用户的手机锁定了屏幕方法,调用这个方法照样可以旋转:

/**

* 强制屏幕转屏

*

* @param orientation 屏幕方向

*/

- (void)interfaceOrientation:(UIInterfaceOrientation)orientation

{

// arc下

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {

SEL selector = NSSelectorFromString(@"setOrientation:");

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];

[invocation setSelector:selector];

[invocation setTarget:[UIDevice currentDevice]];

int val = orientation;

// 从2开始是因为0 1 两个参数已经被selector和target占用

[invocation setArgument:&val atIndex:2];

[invocation invoke];

}

/*

// 非arc下

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {

[[UIDevice currentDevice] performSelector:@selector(setOrientation:)

withObject:@(orientation)];

}

// 直接调用这个方法通不过apple上架审核

[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationLandscapeRight] forKey:@"orientation"];

*/

}

监听设备旋转通知,来处理一些UI显示问题

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(onDeviceOrientationChange)

name:UIDeviceOrientationDidChangeNotification

object:nil

];

原文链接:https://www.jianshu.com/p/5566077bb

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓


http://chatgpt.dhexx.cn/article/y6o33ApS.shtml

相关文章

996程序员办公室猝死?公司:没死,继续上班了

程序员头条 报道 网传 996 程序员办公室猝死&#xff0c;公司回应&#xff1a;系因低血糖晕倒 日前&#xff0c;一则“996程序员办公室猝死”的视频在网络传播。根据视频显示&#xff0c;一名男子扶着办公桌栏短暂逗留之后&#xff0c;直挺挺的摔倒在地面。 事发后&#xff0c;…

网传京东37岁程序员在工位猝死。当事人:我还没死,还能加班!

编程牛人整理 近日微信群里疯传&#xff0c;一男子在工位突然倒地的图片与视频&#xff0c;随后&#xff0c;有自媒体称&#xff0c;这位倒地的员工系京东程序员疑似因常年夜班在工位猝死。 3月25日&#xff0c;网传消息称&#xff0c;该员工今年37岁&#xff0c;在加班过程中猝…

35岁程序员被公司辞退,生活压力太大痛哭,中年危机如何自救?

多数人都喜欢安逸的生活&#xff0c;尤其是随着年龄的增长&#xff0c;很多人都希望工作和生活趋于稳定&#xff0c;不愿意再让生活有很大的变动。可是&#xff0c;当达到一定的年龄时&#xff0c;危机还是存在的。 如今已经35岁的杰哥&#xff0c;是一个典型的理工男&#xf…

秒啊!程序员防猝死指南来了!

‍‍ 作者 | 咏春警告的胖虎 来源 | golang小白成长记&#xff08;ID&#xff1a;golangxbczj&#xff09; 过年之前&#xff0c;跟我可爱的小侄子通了个电话&#xff0c;上来就说&#xff0c;"叔叔你头发怎么变少了"&#xff0c;我很痛心&#xff0c;我的小侄子&…

程序员工作猝死给公司造成损失可以找其父母追讨吗?

从 996.icu 开始&#xff0c;996开始被热议&#xff0c;马巴巴说&#xff1a;“996 是福报”&#xff0c;东哥说&#xff1a;“81168”。然后 90 后乃至 00 后被推出来说&#xff1a;“这届真难带&#xff01;”。我一直认为网上有个段子说得好。  ☠ 月薪5W&#xff1a;996就…

程序员离职事件始末

吴小胖第10次推送 阅读时间预计3分钟~ 这是我毕业后的第一份工作... 面试时&#xff0c;HR小姐姐告诉我... 然鹅...我入职之后才发现&#xff1a; 对标阿里的只有加班强度 对标华为的只有狼性文化 对标百度的&#xff0c;额&#xff0c;没有对标百度 同事们有的住在海淀区、有的…

为什么程序猿996会猝死,而企业家007却不会?

想和吴小胖一起工作吗&#xff1f; 内推邮箱【shen_baili163.com】&#xff0c;响应P99非常小 其实&#xff0c;也可以关注公众号&#xff0c;不取关的那种哦 然后留言&#xff0c;我会主动联系你哟 后面还会继续更新Apache Dubbo源码分析和ElasticSearch的内容哦&#xff0c;大…

唉!一 28 岁程序员因新冠离世。。。

上一篇&#xff1a;为什么总是闹离职的员工没走&#xff0c;平时不吭声的员工却突然离职&#xff1f; 2022年12月19日&#xff0c;微信公众号万户楼台 发布《西安28岁程序员重症病危》的内容。 病人名字为郭晓桐&#xff08;94年生&#xff09;&#xff0c;年龄28岁。 其没有基…

成年人的崩溃只在一瞬间,程序员凌晨三点写的代码竟被女友删了...

对于恋爱中的情侣来说&#xff0c;吵架是很正常的事情&#xff0c;就算是再怎么亲密&#xff0c;也难免会出现意见不合的时候。 吵架不可怕&#xff0c;可怕的是&#xff0c;受吵架情绪的影响&#xff0c;做出一些比较“极端”的事情。 之前某社交平台上一位女生吐槽自己的男…

违法?猝死?你肯定不知道程序员还有这些“高危”操作

全文共2975字&#xff0c;预计学习时长9分钟 图源&#xff1a;百度 10月24日&#xff0c;一段“996程序员猝死在1024程序员节”的视频在各大IT群疯传。不久 “程序员猝死”的消息被搬上了热搜。 图源&#xff1a;微博 看毕&#xff0c;坐在电脑面前的程序员们觉得自己的心跳仿佛…

59% 的程序员曾担心自己猝死!

近期&#xff0c;接连发生的几起猝死事件引发人们的警觉&#xff0c;年轻人健康的话题成为大众持续关注的热点。 1月25日&#xff0c;丁香医生数据研究院发布了《2021国民健康洞察报告》&#xff08;以下简称“报告”&#xff09;&#xff0c;在56196个样本调查中&#xff0c;…

程序员防猝死!!!

自从踏上了这条路&#xff0c;我就早已把生死置之度外了。 如果我死了&#xff0c;只能证明我不过是如此程度的男人。 ​ -----------罗罗诺亚索隆 昨天&#xff0c;微信群里传苏宁程序员上班期间倒地猝死。 这种消息对本身就是程序员的我们&#xff0c;已经不是什么新闻了。…

又一程序员猝死...

声明&#xff1a;本文首发微信公众号【菜鸟要飞】&#xff0c;如有转载&#xff0c;请标明出处&#xff01; 今日在脉脉上看到了一篇不幸的消息&#xff1a;今日头条一名iOS开发猝死了。据网上消息说&#xff0c;此开发曾是腾讯OMG开发组长。 逝者为大&#xff0c;又是同行&am…

又一个程序员倒下-程序员防猝死指南

作者&#xff1a;蒋丽丽 http://news.html5tricks.com/programmer-life-health.html 程序员通常给人的感觉就是高智商、高收入、高压力群体。的确&#xff0c;对于程序员来说&#xff0c;加班可以说真的是家常便饭&#xff0c;虽说是工作8小时制&#xff0c;但是为了赶开发进度…

程序员防猝死指南——程序员养生攻略

程序员在其职业生涯中&#xff0c;健康问题尤为突出。但是大部分程序员只顾码字&#xff0c;却往往忽略了自身的健康问题。这或许是因为写代码太入神&#xff0c;也或许是因为来自老板的压力太大。但这些并不是你折磨自己最好的理由&#xff0c;我们程序员也需要养生&#xff0…

程序员写在猝死的前一天

作者&#xff1a;南喃自愈 今天是我猝死的前一天。 你一定会觉得&#xff0c;真奇怪&#xff0c;一个人怎么会知道自己什么时候猝死呢。 因为我昨晚做了一个梦&#xff0c;梦到一个白胡子老头儿&#xff0c;他说我的阳寿已尽&#xff0c;只剩下一天的寿命。怕我有什么心愿未了&…

网传程序员加班猝死,当事人:我还在写代码

本文转载自程&#xff0c;序员的那些事3 月 25 日&#xff0c;网上又有一个加班猝死的消息刷屏引发热议。网传消息称一位京东 37 岁程序员加班猝死&#xff0c;还配上了抢救视频和照片。 这个消息的源头&#xff0c;或许出自这里&#xff1f; 该截图来自新浪科技的辟谣报道 其…

网传京东 37 岁程序员加班猝死,当事人:我还在写代码,已报警

↓推荐关注↓ 网传京东 37 岁程序员加班猝死 3 月 25 日&#xff0c;网上又有一个加班猝死的消息刷屏引发热议。网传消息称一位京东 37 岁程序员加班猝死&#xff0c;还配上了抢救视频和照片。 这个消息的源头&#xff0c;或许出自这里&#xff1f; 该截图来自新浪科技的辟谣报…

26岁程序员猝死,再次提醒:有几个前兆其实很显眼

猝死在现代社会中可谓是个高频词&#xff0c;且现在猝死的人越来越年轻化。很多人认为猝死就是一瞬间的事&#xff0c;其实不然&#xff0c;当猝死即将到来的时候&#xff0c;身体是会有征兆的&#xff0c;今天小九就和大家仔细探讨这一问题。 痛心&#xff1a;26岁程序员猝死&…

理解 Lambda 表达式

Java8 Stream 相关集合转换 一、初识 Lambda 简化过程&#xff1a; 常规实现接口。静态内部类。局部内部类。匿名内部类。lambda 表达式。lambda 表达式简化。 public class TestMain {static class StaStudent implements Person {Overridepublic void speak(String langu…