前言
本文总结了ios中最常见的视频播放方法,不同的方法都各具特点,我希望能够总结它们的不同,方便在开发中选择合适的技术方案。
apple为我们提供了多种方法来实现视频播放,包括mpmovieplayercontroller,mpmovieplayerviewcontroller,avplayer,avplayerviewcontroller等。而值得注意的是,上述的mpmovieplayercontroller与mpmovieplayerviewcontroller在ios9.0之后被弃用。虽说如此,这还是将它们的用法总结了一下,下面我们简单来了解一下四种播放方式的区别:
ios播放视频.png
温馨提示:代码更直观,首先附上本文demo
一、mpmovieplayercontroller
1.播放视频
mpmoviewplayercontroller继承于nsobject,使用它播放视频需要将其自带的视频view添加到视图控制器的view上才能显示视频,使用步骤如下:
1
2
3
4
|
第一步:引用mediaplayer框架,声明视图控制器属性playercontroller
#import <mediaplayer mediaplayer.h>
@property(nonatomic,strong)mpmovieplayercontroller *playercontroller;
|
1
2
3
4
5
6
7
8
|
//第二步:获取视频路径,创建播放器
//本地视频路径
nsstring* localfilepath=[[nsbundle mainbundle]pathforresource:@ "不能说的秘密" oftype:@ "mp4" ];
nsurl *localvideourl = [nsurl fileurlwithpath:localfilepath];
//网络视频路径
nsstring *webvideopath = @ "http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1115.mp4" ;
nsurl *webvideourl = [nsurl urlwithstring:webvideopath];
self.playercontroller =[[mpmovieplayercontroller alloc]initwithcontenturl:webvideourl];
|
1
2
3
|
//第三步:设置frame将播放器view添加到视图控制器view上
self.playercontroller.view.frame = cgrectmake(0, 10, kdevicewidth, 300);
[self.view addsubview: self.playercontroller.view];
|
1
2
3
4
5
6
7
8
9
|
//第四步:设置播放器属性
//设置控制面板风格:无,嵌入,全屏,默认
self.playercontroller.controlstyle = mpmoviecontrolstyledefault;
//设置是否自动播放(默认为yes)
self.playercontroller.shouldautoplay = no;
//设置播放器显示模式,类似于图片的处理,设置fill有可能造成部分区域被裁剪
self.playercontroller.scalingmode = mpmoviescalingmodeaspectfit;
//设置重复模式
self.playercontroller.repeatmode = mpmovierepeatmodeone;
|
1
2
3
4
5
|
//第五步:播放视频
//播放前的准备,会中断当前正在活跃的音频会话
[ self.playercontroller preparetoplay];
//播放视频,设置了自动播放之后可以不调用此方法
//[ self.playercontroller play];
|
1
2
3
4
5
6
7
8
|
//第六步:在退出界面的时候,关闭播放器,移除通知
- ( void )dealloc{
//当前视图控制器pop之后并不会关闭播放,需要手动关闭
[self.playercontroller stop];
self.playercontroller = nil;
//移除播放器相关的通知
[[nsnotificationcenter defaultcenter] removeobserver:self];
}
|
2.视频播放相关的通知
mpmovieplayercontroller有关视频播放的很多状态控制都是通过通知完成的,尤其是播放在线视频的时候,我们不仅监控视频加载是否成功,也会监控是视频缓存进度等。这里演示一些常用的通知如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//关于通知的使用(还有很多通知可以监听,可查看sdk)
nsnotificationcenter *notificaioncenter = [nsnotificationcenter defaultcenter];
//监听播放器状态的变化
[notificaioncenter addobserver:self
selector:@selector(playerstatechanged:)
name:mpmovieplayerplaybackstatedidchangenotification
object:nil];
//监听播放完成
[notificaioncenter addobserver:self
selector:@selector(playerfinished) name:mpmovieplayerplaybackdidfinishnotification
object:nil];
//监听切换到全屏
[notificaioncenter addobserver:self
selector:@selector(palyerchangefullscreen) name:mpmovieplayerdidenterfullscreennotification
object:nil];
//监听截屏操作完成
[notificaioncenter addobserver:self
selector:@selector(playercapturefinished:) name:mpmovieplayerthumbnailimagerequestdidfinishnotification
object:nil];
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
#pragma mark - 监听通知的响应方法
//播放状态变化,注意播放完成时的状态是暂停
- ( void )playerstatechanged:(nsnotification *)notificaion{
switch (self.playercontroller.playbackstate) {
case mpmovieplaybackstatestopped:{
nslog(@ "播放停止" );
break ;
}
case mpmovieplaybackstateplaying:{
nslog(@ "播放器正在播放" );
break ;
}
case mpmovieplaybackstatepaused:{
nslog(@ "播放器暂停" );
break ;
}
case mpmovieplaybackstateinterrupted:{
nslog(@ "播放器中断" );
break ;
}
case mpmovieplaybackstateseekingforward:{
nslog(@ "播放器快进" );
break ;
}
case mpmovieplaybackstateseekingbackward:{
nslog(@ "播放器快退" );
break ;
}
default :
break ;
}
}
//视频播放结束
- ( void )playerfinished{
nslog(@ "playerfinished:播放结束" );
}
//播放器切换到了全屏
- ( void )palyerchangefullscreen{
nslog(@ "palyerchangefullscreen:播放器进入全屏" );
}
//播放器截屏结束
- ( void )playercapturefinished:(nsnotification *)notification{
//获取并显示截图
uiimage *image=notification.userinfo[mpmovieplayerthumbnailimagekey];
self.captureimgview.image = image;
}
|
3.实现截屏
1
2
3
4
5
6
7
8
9
10
11
|
//添加一个按钮,点击开始截屏
_capturebtn = [[uibutton alloc] initwithframe:cgrectmake(30, cgrectgetmaxy(self.playercontroller.view.frame) + 30, kdevicewidth - 30 * 2, 50)];
_capturebtn.backgroundcolor = [uicolor purplecolor];
[_capturebtn settitle:@ "截图当前屏幕" forstate: uicontrolstatenormal];
[_capturebtn addtarget:self action:@selector(capturecurrentscreenimg) forcontrolevents:uicontroleventtouchupinside];
[self.view addsubview:_capturebtn];
//添加一个imgview 显示截屏后的图片
_captureimgview = [[uiimageview alloc] initwithframe:cgrectmake((kdevicewidth - 150)/2, cgrectgetmaxy(_capturebtn.frame) + 20, 150, 150)];
_captureimgview.contentmode = uiviewcontentmodescaleaspectfit;
_captureimgview.backgroundcolor = [uicolor graycolor];
[self.view addsubview:_captureimgview];
|
1
2
3
4
|
//截取当前屏幕
- ( void )capturecurrentscreenimg{
[self.playercontroller requestthumbnailimagesattimes:@[@(self.playercontroller.currentplaybacktime)] timeoption:mpmovietimeoptionnearestkeyframe];
}
|
1
2
3
4
5
|
//监听通知:播放器截屏结束,得到图片并显示截图
- ( void )playercapturefinished:(nsnotification *)notification{
uiimage *image=notification.userinfo[mpmovieplayerthumbnailimagekey];
self.captureimgview.image = image;
}
|
二、mpmovieplayerviewcontroller
mpmoviceplayerviewcontrolle只能全屏幕播放视频,它是一个包含了mpmovieplayercontroller类型属性的特殊视图控制器,因此它是通过模态视图弹出的方式显示视频的。理解了这个,我们就可以知道在使用mpmoviceplayerviewcontroller的时候我们可以通过它的mpmovieplayercontroller属性设置很多播放器的属性了,具体用法和mpmovieplayercontroller相同,就不过多的解释了,播放视频的代码示例如下;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//第一步:获取视频路径
//本地视频
nsstring* localfilepath=[[nsbundle mainbundle]pathforresource:@ "不能说的秘密" oftype:@ "mp4" ];
nsurl *localvideourl = [nsurl fileurlwithpath:localfilepath];
//在线视频
//nsstring *webvideopath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1115.mp4";
//nsurl *webvideourl = [nsurl urlwithstring:webvideopath];
//第二步:创建视频播放器
mpmovieplayerviewcontroller *playerviewcontroller = [[mpmovieplayerviewcontroller alloc] initwithcontenturl:localvideourl];
//第三步:设置播放器属性
//通过movieplayer属性设置播放器属性(与mpmovieplayercontroller类似)
playerviewcontroller.movieplayer.scalingmode = mpmoviescalingmodefill;
//第四步:跳转视频播放界面
[self presentviewcontroller:playerviewcontroller animated:yes completion:nil];
|
三、avplayer
avplayer相比上述两种方式,播放视频功能更加强大,使用也十分灵活,因为它更加接近底层。但是avplayer本身是不能直接显示视频的,必须创建一个播放层avplayerlayer并将其添加到其他的视图layer上才能显示。
1. 使用avplayer需要了解的常用类
- avasset:一个用于获取多媒体信息的抽象类,但不能直接使用
- avurlasset:avasset的子类,可以根据一个url路径创建一个包含媒体信息的avurlasset对象
- avplayeritem:一个媒体资源管理对象,用于管理视频的基本信息和状态,一个avplayeritem对应一个视频资源
- avplayer:负责视频播放、暂停、时间控制等操作
- avplayerlayer:负责显示视频的图层,如果不设置此属性,视频就只有声音没有图像
2. avplayer的使用步骤
1
2
3
4
|
//第一步:引用avfoundation框架,添加播放器属性
#import <avfoundation/avfoundation.h>
@property (nonatomic,strong)avplayer *player; //播放器对象
@property (nonatomic,strong)avplayeritem *currentplayeritem;
|
1
2
3
4
5
6
7
|
//第二步:获取播放地址url
//本地视频路径
nsstring* localfilepath=[[nsbundle mainbundle]pathforresource:@ "不能说的秘密" oftype:@ "mp4" ];
nsurl *localvideourl = [nsurl fileurlwithpath:localfilepath];
//网络视频路径
nsstring *webvideopath = @ "http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1129.mp4" ;
nsurl *webvideourl = [nsurl urlwithstring:webvideopath];
|
1
2
3
4
5
6
7
8
|
//第三步:创建播放器(四种方法)
//如果使用url创建的方式会默认为avplayer创建一个avplayeritem
//self.player = [avplayer playerwithurl:localvideourl];
//self.player = [[avplayer alloc] initwithurl:localvideourl];
//self.player = [avplayer playerwithplayeritem:playeritem];
avplayeritem *playeritem = [[avplayeritem alloc] initwithurl:webvideourl];
self.currentplayeritem = playeritem;
self.player = [[avplayer alloc] initwithplayeritem:playeritem];
|
1
2
3
4
5
6
7
8
9
10
11
|
//第四步:创建显示视频的avplayerlayer,设置视频显示属性,并添加视频图层
//contentview是一个普通view,用于放置视频视图
/*
avlayervideogravityresizeaspectfill等比例铺满,宽或高有可能出屏幕
avlayervideogravityresizeaspect 等比例 默认
avlayervideogravityresize 完全适应宽高
*/
avplayerlayer *avlayer = [avplayerlayer playerlayerwithplayer:self.player];
avlayer.videogravity = avlayervideogravityresizeaspect;
avlayer.frame = _containerview.bounds;
[_containerview.layer addsublayer:avlayer];
|
1
2
3
4
|
//第六步:执行play方法,开始播放
//本地视频可以直接播放
//网络视频需要监测avplayeritem的status属性为avplayerstatusreadytoplay时方法才会生效
[self.player play];
|
3. 添加属性观察
一个avplayeritem对象对应着一个视频,我们需要通过avplayeritem来获取视频属性。但是avplayeritem必须是在视频资源加载到可以播放的时候才能使用,这是受限于网络的原因。解决这一问题,我们需要使用kvo监测avplayeritem的status属性,当其为avplayeritemstatusreadytoplay的时候我们才能获取视频相关属性。相关的代码示例如下:
1
2
3
4
5
|
//1.注册观察者,监测播放器属性
//观察status属性,可以在加载成功之后得到视频的长度
[self.player.currentitem addobserver:self forkeypath:@ "status" options:nskeyvalueobservingoptionnew context:nil];
//观察loadedtimeranges,可以获取缓存进度,实现缓冲进度条
[self.player.currentitem addobserver:self forkeypath:@ "loadedtimeranges" options:nskeyvalueobservingoptionnew context:nil];
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
//2.添加属性观察
- ( void )observevalueforkeypath:(nsstring *)keypath
ofobject:(id)object
change:(nsdictionary *)change
context:( void *)context {
avplayeritem *playeritem = (avplayeritem *)object;
if ([keypath isequaltostring:@ "status" ]) {
//获取playeritem的status属性最新的状态
avplayerstatus status = [[change objectforkey:@ "new" ] intvalue];
switch (status) {
case avplayerstatusreadytoplay:{
//获取视频长度
cmtime duration = playeritem.duration;
//更新显示:视频总时长(自定义方法显示时间的格式)
self.totalneedplaytimelabel.text = [self formattimewithtimeinterval:cmtimegetseconds(duration)];
//开启滑块的滑动功能
self.sliderview.enabled = yes;
//关闭加载loading提示
[self showaativityindicatorview:no];
//开始播放视频
[self.player play];
break ;
}
case avplayerstatusfailed:{ //视频加载失败,点击重新加载
[self showaativityindicatorview:no]; //关闭loading视图
self.playerinfobutton.hidden = no; //显示错误提示按钮,点击后重新加载视频
[self.playerinfobutton settitle:@ "资源加载失败,点击继续尝试加载" forstate: uicontrolstatenormal];
break ;
}
case avplayerstatusunknown:{
nslog(@ "加载遇到未知问题:avplayerstatusunknown" );
break ;
}
default :
break ;
}
} else if ([keypath isequaltostring:@ "loadedtimeranges" ]) {
//获取视频缓冲进度数组,这些缓冲的数组可能不是连续的
nsarray *loadedtimeranges = playeritem.loadedtimeranges;
//获取最新的缓冲区间
cmtimerange timerange = [loadedtimeranges.firstobject cmtimerangevalue];
//缓冲区间的开始的时间
nstimeinterval loadstartseconds = cmtimegetseconds(timerange.start);
//缓冲区间的时长
nstimeinterval loaddurationseconds = cmtimegetseconds(timerange.duration);
//当前视频缓冲时间总长度
nstimeinterval currentloadtotaltime = loadstartseconds + loaddurationseconds;
//nslog(@"开始缓冲:%f,缓冲时长:%f,总时间:%f", loadstartseconds, loaddurationseconds, currentloadtotaltime);
//更新显示:当前缓冲总时长
_currentloadtimelabel.text = [self formattimewithtimeinterval:currentloadtotaltime];
//更新显示:视频的总时长
_totalneedloadtimelabel.text = [self formattimewithtimeinterval:cmtimegetseconds(self.player.currentitem.duration)];
//更新显示:缓冲进度条的值
_progressview.progress = currentloadtotaltime/cmtimegetseconds(self.player.currentitem.duration);
}
}
|
1
2
3
4
5
6
7
8
|
//转换时间格式的方法
- (nsstring *)formattimewithtimeinterval:(nstimeinterval)timeinterval{
int minute = 0, hour = 0, secend = timeinterval;
minute = (secend % 3600)/60;
hour = secend / 3600;
secend = secend % 60;
return [nsstring stringwithformat:@ "%02d:%02d:%02d" , hour, minute, secend];
}
|
4. 获取当前播放时间与总时间
在此之前我们需要首先了解一个数据类型,也就是上述操作中的cmtime, 在avplayer的使用中我们会经常用到它,其实cmtime是一个结构体如下:
1
2
3
4
5
6
|
typedef struct {
cmtimevalue value; // 帧数
cmtimescale timescale; // 帧率(影片每秒有几帧)
cmtimeflags flags;
cmtimeepoch epoch;
} cmti
|
在上面的操作中我们看到avplayeritem的duration属性就是一个cmtime类型的数据。所以获取视频的总时长(秒)需要duration.value/duration.timescale。当然系统也为我们提供了cmtimegetseconds函数更加方便计算:
总时长: duration.value == cmtimegetseconds(duration) 。
在快进视频到某一个位置的时候我们也需要创建cmtime作为参数,那么cmtime的创建方法有两种:
1
2
3
4
5
|
//方法1:
cmtimemakewithseconds(flout64 seconds, int32_t scale)
//方法2:
cmtimemake(int64_t value, int32_t scale)
//注:两者的区别在于方法一的第一个参数可以是float
|
至于获取视频的总时间在上述代码中已有体现,是在检测播放状态变为avplayerstatusreadytoplay的时候获取的
1
2
3
4
5
6
|
//视频总时长,在avplayeritem状态为avplayerstatusreadytoplay时获取
cmtime duration = self.player.currentitem.duration;
cgfloat totaltime = cmtimegetseconds(duration);
//当前avplayer的播放时长
cmtime cmtime = self.player.currenttime;
cgfloat currenttime = cmtimegetseconds(cmtime);
|
5. 播放进度与状态的刷新
实时更新当前播放时间,这时候我们不必使用定时器,因为avplayer已经提供了方法:
addperiodictimeobserverforinterval: queue: usingblock。当播放进度改变的时候方法中的回调会被执行。我们可以在这里做刷新时间的操作,代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
|
__weak __typeof(self) weakself = self;
[self.player addperiodictimeobserverforinterval:cmtimemake(1, 1) queue:dispatch_get_main_queue() usingblock:^(cmtime time ) {
//当前播放的时间
nstimeinterval currenttime = cmtimegetseconds( time );
//视频的总时间
nstimeinterval totaltime = cmtimegetseconds(weakself.player.currentitem.duration);
//设置滑块的当前进度
weakself.sliderview.value = currenttime/totaltime;
//设置显示的时间:以00:00:00的格式
weakself.currenttimelabel.text = [weakself formattimewithtimeinterval:currenttime];
}];
|
6. 滑块拖拽修改视频播放进度
1
2
3
4
5
6
7
8
9
|
//uislider的响应方法:拖动滑块,改变播放进度
- (ibaction)sliderviewchange:(id)sender {
if (self.player.status == avplayerstatusreadytoplay){
nstimeinterval playtime = self.sliderview.value * cmtimegetseconds(self.player.currentitem.duration);
cmtime seektime = cmtimemake(playtime, 1);
[self.player seektotime:seektime completionhandler:^( bool finished) {
}];
}
}
|
四、avplayerviewcontroller
avplayerviewcontroller是ios8新增视频框架avkit中的一个播放器类。由于ios9弃用前两种播放器类的原因,avplayerviewcontroller也将变得更加常用。avplayerviewcontroller适合开发播放界面要求不是很高的应用。其相比avplayer的使用更加方便,但是原理上还是avplayerviewcontroller包含了一个avplayer对象。
avplayerviewcontroller有两种播放视频的方式:
第一种:直接弹出模态视图控制器播放
1
2
3
4
5
6
7
8
9
|
//步骤1:获取视频路径
nsstring *webvideopath = @ "http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1213.mp4" ;
nsurl *webvideourl = [nsurl urlwithstring:webvideopath];
//步骤2:创建avplayer
avplayer *avplayer = [[avplayer alloc] initwithurl:webvideourl];
//步骤3:使用avplayer创建avplayerviewcontroller,并跳转播放界面
avplayerviewcontroller *avplayervc =[[avplayerviewcontroller alloc] init];
avplayervc.player = avplayer;
[self presentviewcontroller:avplayervc animated:yes completion:nil];
|
第二种:添加avplayerviewcontroller的view到父视图上播放。
使用这种方式播放的优点在于可以指定播放界面的原始尺寸大小,但是值得注意的是avplayerviewcontroller必须被当前视图控制器所持有,以防止被当做局部变量被释放。为了满足这一条件,我们可以将avplayerviewcontroller作为属性,也可以使用addchildviewcontroller方法将其作为当前视图控制器的子视图控制器,示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//步骤1:获取视频路径
nsstring *webvideopath = @ "http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1213.mp4" ;
nsurl *webvideourl = [nsurl urlwithstring:webvideopath];
//步骤2:创建avplayer
avplayer *avplayer = [[avplayer alloc] initwithurl:webvideourl];
//步骤3:使用avplayer创建avplayerviewcontroller,并跳转播放界面
avplayerviewcontroller *avplayervc =[[avplayerviewcontroller alloc] init];
avplayervc.player = avplayer;
//步骤4:设置播放器视图大小
avplayervc.view.frame = cgrectmake(25, 0, 320, 300);
//特别注意:avplayerviewcontroller不能作为局部变量被释放,否则无法播放成功
//解决1.avplayerviewcontroller作为属性
//解决2:使用addchildviewcontroller,avplayerviewcontroller作为子视图控制器
[self addchildviewcontroller:avplayervc];
[self.view addsubview:avplayervc.view];
|
最后总结:
以上就是ios视频播放的基本方法,但这里也仅限一些基础的播放需求。若要实现更为复杂的播放功能,仍然有很多东西需要我们继续深入研究,加油!
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://www.jianshu.com/p/b304694af77a