XLVideoPlayer的学习笔记

本文聊点关于最近写的这个自定义播放器。支持UITableViewCell上小屏、全屏播放,手动及屏幕旋转切换,包括右下角的小窗悬停播放,不依赖于视图控制器和第三方,尽量的让使用起来更简单,具体代码详情请戳Github,先看看效果如何!

这是基于AVFoundation下自定义的一个播放器,先简单介绍几个用到的类。

介绍:

AVPlayer:可以理解为播放器对象,灵活性好,可以高度化的自定义UI,但它本身不能显示视频,显示需要另一个类AVPlayerLayer来显示,继承于CALayer,下面是摘自官方的一段介绍

AVPlayer works equally well with local and remote media files.

You can display the visual content of items played by an instance of AVPlayer in a CoreAnimation layer of class AVPlayerLayer.

You can observe the status of a player using key-value observing.

主要是说它支持本地/网络媒体播放,需要CoreAnimation下的AVPlayerLayer来显示视频,我们可以通过KVO监听player的播放状态。

AVPlayerItem:存有相关媒体信息的类,一个视频资源对应一个AVPlayerItem对象,当你需要循环播放多个视频资源时也需创建多个AVPlayerItem对象。建议大家可以多看看官方的英文文档解释(题外话)。

An AVPlayerItem represents the presentation state of an asset that’s played by an AVPlayer object, and lets you observe that state.

AVAsset:主要用于获取多媒体信息,可以理解为一个抽象类,不能直接使用,操作针对它的子类AVURLAsset,根据你视频的url创建一个包含视频媒体信息的AVURLAsset对象。

CMTime:还会用到这个媒体时间相关的类,如有不明白可以看之前一个帖子的解释。

层级关系:

基于以上几个类就能实现视频的基本功能了,例如暂停、播放,快进、后退、显示播放/缓冲进度。然后UI层面,层级很简单,XLVideoPlayer继承于UIView,上面我们说到显示视频需要AVPlayerLayer,我们将AVPlayerLayer加到view的layer上。

下面贴出主要的代码,初始化AVPlayer对象

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- (AVPlayerLayer *)playerLayer {

if(!_playerLayer) {

_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];

_playerLayer.backgroundColor = kPlayerBackgroundColor;

_playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;//视频填充模式

}

return_playerLayer;

}

- (AVPlayer *)player{

if(!_player) {

AVPlayerItem *playerItem = [self getAVPlayItem];

self.playerItem = playerItem;

_player = [AVPlayer playerWithPlayerItem:playerItem];

[self addProgressObserver];

[self addObserverToPlayerItem:playerItem];

}

return_player;

}

//initialize AVPlayerItem

- (AVPlayerItem *)getAVPlayItem{

NSAssert(self.videoUrl != nil, @"必须先传入视频url!!!");

if([self.videoUrl rangeOfString:@"http"].location != NSNotFound) {

AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:[NSURL URLWithString:[self.videoUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];

returnplayerItem;

}else{

AVAsset *movieAsset  = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:self.videoUrl] options:nil];

AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:movieAsset];

returnplayerItem;

}

}

同时我们注册KVO,监控视频播放过程,这可以获取视频的播放进度。AVPlayer有一个属性currentItem是AVPlayerItem类型,表示当前播放的视频对象。

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#pragma mark - monitor video playing course

-(void)addProgressObserver{

//get current playerItem object

AVPlayerItem *playerItem = self.player.currentItem;

__weaktypeof(self) weakSelf = self;

//Set once per second

[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {

float current = CMTimeGetSeconds(time);

float total = CMTimeGetSeconds([playerItem duration]);

weakSelf.progressLabel.text = [weakSelf timeFormatted:current];

if(current) {

//            NSLog(@"%f", current / total);

weakSelf.slider.value = current / total;

if(weakSelf.slider.value == 1) {//complete block

if(weakSelf.completedPlayingBlock) {

weakSelf.completedPlayingBlock(weakSelf);

}else{//finish and loop playback

weakSelf.playOrPauseBtn.selected = NO;

[weakSelf showOrHidenBar];

CMTime currentCMTime = CMTimeMake(0, 1);

[weakSelf.player seekToTime:currentCMTime completionHandler:^(BOOL finished) {

weakSelf.slider.value = 0.0f;

}];

}

}

}

}];

}

以及监听AVPlayerItem对象的status/loadedTimeRanges属性变化,status对应播放状态,loadedTimeRanges网络缓冲状态,当loadedTimeRanges的改变时,每缓冲一部分数据就会更新此属性,可以获得本次缓冲加载的视频范围(包含起始时间、本次网络加载时长)

1

2

3

4

5

6

7#pragma mark - PlayerItem (status,loadedTimeRanges)

-(void)addObserverToPlayerItem:(AVPlayerItem *)playerItem{

//监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态

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

//network loading progress

[playerItem 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- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{

AVPlayerItem *playerItem = object;

if([keyPath isEqualToString:@"status"]) {

AVPlayerStatus status = [[change objectForKey:@"new"] intValue];

if(status == AVPlayerStatusReadyToPlay){

self.totalDuration = CMTimeGetSeconds(playerItem.duration);

self.totalDurationLabel.text = [self timeFormatted:self.totalDuration];

}

}elseif([keyPath isEqualToString:@"loadedTimeRanges"]){

NSArray *array = playerItem.loadedTimeRanges;

CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围

float startSeconds = CMTimeGetSeconds(timeRange.start);

float durationSeconds = CMTimeGetSeconds(timeRange.duration);

NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度

self.slider.middleValue = totalBuffer / CMTimeGetSeconds(playerItem.duration);

//        NSLog(@"totalBuffer:%.2f",totalBuffer);

//remove loading animation

if(self.slider.middleValue <= self.slider.value) {

self.activityIndicatorView.center = self.center;

[self addSubview:self.activityIndicatorView];

[self.activityIndicatorView startAnimating];

}else{

[self.activityIndicatorView removeFromSuperview];

}

}

}

下面这部分是定位视频的某个位置播放,也就是快进后退。

这里需要注意的是在用户拖拽slider的过程中需要先暂停,否则手动改变进度和播放的进度会有冲突,用户拖拽完毕再去播放视频。

1

2

3

4

5

6

7

8

9- (void)finishChange {

_inOperation = NO;

[self hiden];

CMTime currentCMTime = CMTimeMake(self.slider.value * self.totalDuration,1);

[self.player seekToTime:currentCMTime completionHandler:^(BOOL finished) {

[self.player play];

self.playOrPauseBtn.selected = YES;

}];

}

关于屏幕旋转

这部分还是遇到一些坑,可以看到并没有在plist文件设置工程支持横屏,所有都是通过强制旋转屏幕实现,在用户旋转屏幕的通知或者点击事件中调用强制旋转的代码。会发现当你旋转屏幕时,其实UITableView和其他控件是不会随屏幕一起旋转的,强制旋转涉及到iOS8+和之前的系统的问题,当我们调用之前的时,在iOS7和iOS8+的效果是不一样的,我从网上摘了来两个图。

1

[[UIApplication sharedApplication] setStatusOrientation:XX]

第一张图iOS 7的,第二张图是iOS 8+,很明显我们发现iOS7当你调用这个方法UIscreen和UIWindow一起转过来了,而iOS8后UIScreen并没有转过来,这样就会导致调用这个方法在iOS8+会存在部分区域点击无响应,因为它超出UIScreen的那部分范围,而且我在测试过程中还发现用这种方法旋转在点击Home键再次进入程序会导致屏幕错位。

怎么办呢!后面又找到这个方法:

1

[[UIDevice currentDevice]setOrientation:UIInterfaceOrientationPortrait];

但是现在苹果已经将该方法私有化了,直接pass掉。之后在stackoverflow做了些尝试,找到现在用的这个方法,它并没有把系统的status bar旋转过来。

1

2NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];

[[UIDevice currentDevice] setValue:value forKey:@"orientation"];

之后还查了一些相关的东西,有兴趣大家可以看看:

详解UICoordinateSpace和UIScreen在iOS 8上的坐标问题

屏幕旋转学习笔记

写一个播放器还需要注意很多细节,只能根据需求一步步的完善,这里只能说一些需要关注的点。如果大家觉得不错希望可以在点击右上角Star,谢谢支持!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,763评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,728评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,834评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,707评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,576评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,429评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,826评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,474评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,762评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,792评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,582评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,424评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,845评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,059评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,361评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,826评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,038评论 2 338

推荐阅读更多精彩内容