iOS开发--XLVideoPlayer——基于AVFoundation自定义的视频播放器

本文聊点关于最近写的这个自定义播放器。支持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]]];

        return playerItem;

    }else{

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

        AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:movieAsset];

        return playerItem;

    }

}

同时我们注册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;

    __weak typeof(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];

        }

    }else if([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

2

NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];

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

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

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

屏幕旋转学习笔记

写一个播放器还需要注意很多细节,只能根据需求一步步的完善,这里只能说一些需要关注的点。

时间: 2024-08-09 02:18:38

iOS开发--XLVideoPlayer——基于AVFoundation自定义的视频播放器的相关文章

iOS开发进阶 - 用AVFoundation自定义视频录制功能

如果移动端访问不佳,请访问我的个人博客 系统自带的录制视频的功能显然无法满足美工和项目经理的要求,自定义视频录制就非常重要了,那么下面来带大家制作属于自己的视频录制界面. 简介 自定义视频录制需要用到的框架主要是AVFoundation和CoreMedia,包括视频输出,输入和文件的读写,下面给大家罗列一下将要用到的类: AVCaptureSession AVCaptureVideoPreviewLayer AVCaptureDeviceInput AVCaptureConnection AVC

iOS开发项目篇—28自定义UITextView

iOS开发项目篇—28自定义UITextView 一.简单说明 1.要实现的效果 2.分析 (1)UITextField 1.最多只能输入一行文字 2.能设置提醒文字(placehoder) 3.不具备滚动功能 (2)UITextView 1.能输入N行文字(N>0) 2.不能设置提醒文字(没有placehoder属性) 3.具备滚动功能 需求:技能输入多行文字,又具备文字提醒功能. 这里选择自定义一个类,让其继承自UITextView类,为其添加一个设置文字提醒的功能. 二.实现 自定义UI控

iOS开发项目篇—29自定义工具条

iOS开发项目篇—29自定义工具条 一.简单说明 1.实现效果: 2.实现思路: (1)尝试: 1 //添加子控件 2 -(void)setupTextView 3 { 4 //1.创建输入控件 5 YYTextView *textView=[[YYTextView alloc]init]; 6 //设置frame 7 textView.frame=self.view.bounds; 8 [self.view addSubview:textView]; 9 self.textView=textV

iOS开发项目篇—27自定义UITabBar

iOS开发项目篇—27自定义UITabBar 一.自定义 思路: (1)新建一个继承自UITabBar的类,自定义一个UITabBar (2)用自定义的UITabBar换掉系统的UItabBar(使用了KVC) (3)监听控制器的切换,只要控制器一切换,就调用代理方法强制重新布局子控件(内部会调用layoutSubviews). YYTabBar.m文件代码: 1 // 2 // YYTabBar.m 3 // 4 5 #import "YYTabBar.h" 6 7 @interfa

100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】

转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 简介 流程图 simplest_ffmpeg_player标准版代码 simplest_ffmpeg_player_suSU版代码 结果 FFMPEG相关学习资料 补充问题 ===================================================== 最简单的基于FFmp

最简单的基于FFMPEG+SDL的视频播放器 ver2 (採用SDL2.0)

===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x) 最简单的基于FFMPEG+SDL的视频播放器 ver2 (採用SDL2.0) 最简单的基于FFmpeg的解码器-纯净版(不包括libavformat) 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器 最简单的基于FFMPEG的Hellowor

最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

简介 之前做过一个FFMPEG+SDL的简单播放器:<100行代码实现最简单的基于FFMPEG+SDL的视频播放器>.该播放器采用SDL1.2显示视频.最近有不少人反映SDL已经升级到2.0版本了,甚至官网的Wiki上都只有SDL2.0的文档了,因此下载了SDL 2.0 并且进行了简单的研究.随后对此前的播放器进行了修改,将SDL1.2换成了SDL2.0. 注:<100行代码实现最简单的基于FFMPEG+SDL的视频播放器>文章中提到的很多知识这里不再重复.本文重点记录SDL1.2

最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器

本文补充记录<最简单的基于FFMPEG+SDL的视频播放器>中的两个例子:FFmpeg视频解码器和SDL像素数据播放器.这两个部分是从视频播放器中拆分出来的两个例子.FFmpeg视频解码器实现了视频数据到YUV数据的解码,而SDL像素数据播放器实现了YUV数据的显示.简而言之,原先的FFmpeg+SDL视频播放器实现了: 视频数据->YUV->显示器 FFmpeg视频解码器实现了: 视频数据->YUV SDL像素数据播放器实现了: YUV->显示器 FFmpeg视频解码

用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”

FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua1020/article/details/8652605. 用JavaCV重新实现并使用opencv_highgui进行显示. 1 import com.googlecode.javacpp.IntPointer; 2 import com.googlecode.javacpp.Pointer; 3 im