代码地址如下:
http://www.demodashi.com/demo/11685.html

AVPlayer 是一个强大的视频播放器,可以播放多种格式的视频,缺点是没有控制界面,需要自己去实现。

一、效果图

二、实现过程

先看下它的结构



首先初始化播放器,设置播放URL。

self.avPlayerView = [[XYAVPlayerView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, VIDEO_HEIGHT)];
self.avPlayerView.videoUrl = m3u;
[self.view addSubview:self.avPlayerView];

初始化方法,添加一个视频控制器。


- (instancetype)initWithFrame:(CGRect)frame
{
if ([super initWithFrame:frame]) {
self.backgroundColor = [UIColor blackColor];
[self addSubview:self.playControlView];
}
return self;
}

设置视频URL


- (void)setVideoUrl:(NSString *)videoUrl
{
_videoUrl = videoUrl.copy;
[self createAVPlayer]; }

初始化 AVPlayer,给_playControlView 引用AVPlayer,方便进行控制, [_playControlView addObserver]; [_playControlView playerTimerAction];会在后面说明。


- (void)createAVPlayer
{ NSURL *url = [NSURL URLWithString:self.videoUrl];
if (!url) {
return;
} /**
* AVPlayer
*/
_avPlayerItem = [AVPlayerItem playerItemWithURL:url];
_avPlayer = [AVPlayer playerWithPlayerItem:_avPlayerItem];
_avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];
_avPlayerLayer.frame = self.bounds;
_avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspect;// [self.layer addSublayer:_avPlayerLayer]; [_avPlayer play]; self.autoresizesSubviews = YES; //子视图Size自适应 _playControlView.avPlayer = self.avPlayer;
_playControlView.avPlayerItem = self.avPlayerItem;
_playControlView.avPlayerLayer = self.avPlayerLayer; [_playControlView addObserver];
[_playControlView playerTimerAction]; [self bringSubviewToFront:self.playControlView];
}

到这里只是创建了一个View,上面加载了一个AVPlayer,一个视频控制器视图。

视频控制器代码:

我用的xib创建的View,所以初始化方法是awakeFromNib


- (void)awakeFromNib
{
[super awakeFromNib]; [self configureVolume]; UITapGestureRecognizer * screenTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(screenTap)];
[self addGestureRecognizer:screenTap]; UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRecognizerAction:)];
pan.delegate = self; [_adjustView addGestureRecognizer:pan]; [self.slider setThumbImage:[UIImage imageNamed:@"verify_code_button"] forState:UIControlStateNormal]; // slider开始滑动事件
[_slider addTarget:self action:@selector(progressSliderTouchBegan:) forControlEvents:UIControlEventTouchDown];
// slider滑动中事件
[_slider addTarget:self action:@selector(progressSliderValueChanged:) forControlEvents:UIControlEventValueChanged];
// slider结束滑动事件
[_slider addTarget:self action:@selector(progressSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchUpOutside];
// slider 添加点击手势
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(sliderTap:)];
[_slider addGestureRecognizer:tap]; [self hiddenViews]; }

/**
* 获取系统音量
*/
- (void)configureVolume { MPVolumeView *volumeView = [[MPVolumeView alloc] init];
_volumeSlider = nil;
for (UIView *view in [volumeView subviews]){
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
_volumeSlider = (UISlider *)view;
break;
}
} // 使用这个category的应用不会随着手机静音键打开而静音,可在手机静音下播放声音
NSError *setCategoryError = nil;
BOOL success = [[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryPlayback
error: &setCategoryError]; if (!success) { /* handle the error in setCategoryError */ } }

第一个手势screenTap,是控制下方控制条的显示与隐藏的

- (void)screenTap {
self.controlStripView.hidden = !self.controlStripView.hidden;
}

第二个手势则是用来控制快进、快退、音量、亮度调节的

- (void)panGestureRecognizerAction: (UIPanGestureRecognizer *)sender {

    //根据在view上Pan的位置,确定是调音量还是亮度
CGPoint locationPoint = [sender locationInView:self]; CGPoint veloctyPoint = [sender velocityInView:self]; switch (sender.state) {
case UIGestureRecognizerStateBegan:{
// 使用绝对值来判断移动的方向
CGFloat x = fabs(veloctyPoint.x);
CGFloat y = fabs(veloctyPoint.y); if (x > y) { //水平移动
[self showViews];
[self playerPause];
self.pandirection = PanDirectionHorizontalMoved;
}else { //垂直移动
self.pandirection = PanDirectionVerticalMoved;
}
break;
}
case UIGestureRecognizerStateChanged:{
switch (_pandirection) {
case PanDirectionHorizontalMoved:{
[self horizontalMoved:veloctyPoint.x];
break;
}
case PanDirectionVerticalMoved:{
if (locationPoint.x > self.bounds.size.width / 2) {//音量调节-右侧
[self verticalMovedForVolume:veloctyPoint.y];
}else {//亮度调节-左侧
[self verticalMovedForBrightness:veloctyPoint.y];
}
break;
}
}
break;
}
case UIGestureRecognizerStateEnded:{
switch (_pandirection) {
case PanDirectionHorizontalMoved:{
[self hiddenViews];
[self playerPlay];
break;
}
case PanDirectionVerticalMoved:{
break;
}
}
break;
}
default:break;
}
}

对于slider的方法,在开始手势的时候暂停视频播放,显示时间label


- (void)progressSliderTouchBegan: (UISlider *)sender { self.changeTimeLabel.hidden = NO; [self playerPause]; NSLog(@" --- began touch");
}

在开始手势的时候seek到slider对应时间的时间点,然后开始视频播放,隐藏时间label


- (void)progressSliderTouchEnded: (UISlider *)sender {
CMTime durationTime = self.avPlayerItem.duration; NSTimeInterval currentTime = CMTimeGetSeconds(durationTime) * sender.value; [self seekWithTime:currentTime]; self.changeTimeLabel.hidden = YES; [self playerPlay]; NSLog(@" ---- end touch");
}

slider滑动的时候,只是改变label上显示的时间


- (void)progressSliderValueChanged: (UISlider *)sender { CGFloat currentTime = sender.value * CMTimeGetSeconds(self.avPlayerItem.duration); NSString *tempCurrentTime = [self timeFormatterForServiceWithTimeStamp:currentTime]; //当前播放时间
self.currentTimeLabel.text = tempCurrentTime; //屏幕中间时间
self.changeTimeLabel.text = tempCurrentTime; CGFloat sliderProgress = sender.value / sender.maximumValue; if (self.progressView.progress < sliderProgress) {
self.progressView.progress = sender.value / sender.maximumValue;
} NSLog(@" --- event touch %f",sender.value); }

slider的单击方法则是直接seek到对应时间点,AVPlayer会自动处理的。


/**
* Slider Tap
*/
- (void)sliderTap: (UITapGestureRecognizer *)sender { if ([sender.view isKindOfClass:[UISlider class]]) { UISlider *slider = (UISlider *)sender.view;
CGPoint point = [sender locationInView:slider];
CGFloat length = slider.frame.size.width; CGFloat tempValue = point.x / length; NSTimeInterval currentTime = CMTimeGetSeconds(self.avPlayerItem.duration) * tempValue; CGFloat progress = currentTime/CMTimeGetSeconds(self.avPlayerItem.duration); if (progress > slider.value) {
self.progressView.progress = progress;
} [self seekWithTime:currentTime];
} }

前面用的 addObserver方法,则是给播放器添加观察者,用来检测播放器状态,还有APP的状态。


- (void)addObserver
{
//监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
[self.avPlayerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//监控网络加载情况属性
[self.avPlayerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; /**
* 进入后台 暂停播放
*
*/
[kNotificationCenter addObserver:self selector:@selector(applicationDidEnterBackground_Notification) name:ApplicationDidEnterBackground_Notification object:nil]; /**
* 进入活跃状态 继续播放
*
*/
[kNotificationCenter addObserver:self selector:@selector(applicationDidBecomeActive_Notification) name:ApplicationDidBecomeActive_Notification object:nil]; [kNotificationCenter addObserver:self selector:@selector(playerReset) name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; } /** * 通过KVO监控播放器状态 *
* @param keyPath 监控属性
* @param object 监视器
* @param change 状态改变
* @param context 上下文 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
AVPlayerItem *playerItem=object;
if ([keyPath isEqualToString:@"status"]) {
AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
if(status==AVPlayerStatusReadyToPlay){
//总时长
self.allTimeLabel.text = [self timeFormatterForServiceWithTimeStamp:CMTimeGetSeconds(playerItem.duration)];
self.currentTimeLabel.text = @"00:00:00"; NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(playerItem.duration));
}
}
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;//缓冲总长度
NSLog(@"共缓冲:%.2f",totalBuffer);
double ableScale = totalBuffer / CMTimeGetSeconds(playerItem.duration);
if (ableScale <= 1) {
self.progressView.progress = ableScale;
}
}
}

playerTimerAction方法则是制定每秒进行一次返回,返回当前播放进度。


- (void)playerTimerAction
{
__weak XYAVControlView * weakSelf = self; [self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { float current=CMTimeGetSeconds(time);
if (current) {
//当前播放进度
weakSelf.currentTimeLabel.text = [weakSelf timeFormatterForServiceWithTimeStamp:CMTimeGetSeconds(weakSelf.avPlayerItem.currentTime)];
//滑块进度
double totalTempTime = CMTimeGetSeconds(weakSelf.avPlayerItem.duration);
double scale = CMTimeGetSeconds(weakSelf.avPlayerItem.currentTime) / totalTempTime;
weakSelf.slider.value = scale;
}
}];
}

最后在 ViewController 里面监控屏幕方向的变化,来处理全屏效果


[kNotificationCenter addObserver:self
selector:@selector(onDeviceOrientationChange)
name:UIDeviceOrientationDidChangeNotification
object:nil]; /**
* 屏幕方向发生变化会调用这里
*/
- (void)onDeviceOrientationChange
{
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;
switch (interfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:
case UIInterfaceOrientationPortrait:{
self.view.frame = (CGRect){0,0,ScreenWidth,ScreenHeight};
self.avPlayerView.frame = CGRectMake(0, 0,ScreenWidth,VIDEO_HEIGHT);
self.avPlayerView.playControlView.isFullScreen = NO;
break;
}
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:{
self.view.frame = (CGRect){0,0,ScreenWidth,ScreenHeight};
self.avPlayerView.frame = self.view.bounds;
self.avPlayerView.playControlView.isFullScreen = YES;
break;
}
default:
break;
}
}

到这里一个简单的视频播放器就做完了。

三、项目代码截图

第一层目录结构

第二层目录结构



使用AVPlayer制作一个播放器

代码地址如下:
http://www.demodashi.com/demo/11685.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

使用AVPlayer制作一个播放器的更多相关文章

  1. HTML+纯JS制作音乐播放器

    该篇文章会教你通过JavaScript制作一个简单的音乐播放器.包括播放.暂停.上一曲和下一曲. 阅读本文章你需要对HTML.CSS和Javascript有基本的了解. 话不多说,先上图. emmm. ...

  2. 使用VideoView自定义一个播放器控件

    介绍 最近要使用播放器做一个简单的视频播放功能,开始学习VideoView,在横竖屏切换的时候碰到了点麻烦,不过在查阅资料后总算是解决了.在写VideoView播放视频时候定义控制的代码全写在Actv ...

  3. 业余写的一个播放器SDK,求点意见

    好久没写博客了 现大致花了半年时间私下写一个音频SDK,想请csdn的达人提点意见,看看还需要增加哪些功能 我对这个的定位如下: 可以在游戏开发中播放音乐,作为一般的音频播放器后端,作为音频编辑器后端 ...

  4. 使用Mediaplay类写一个播放器

    我们知道android本身播放视频的的能力是有限的..先来一个Demo 另附我的一个还未成熟的播放器,下载地址:http://www.eoemarket.com/soft/370334.html,正在 ...

  5. Android 音视频深入 十三 OpenSL ES 制作音乐播放器,能暂停和调整音量(附源码下载)

    项目地址https://github.com/979451341/OpenSLAudio OpenSL ES 是基于NDK也就是c语言的底层开发音频的公开API,通过使用它能够做到标准化, 高性能,低 ...

  6. 【C++】从零开始,只使用FFmpeg,Win32 API,实现一个播放器(一)

    前言 起初只是想做一个直接读取视频文件然后播放字符动画的程序.我的设想很简单,只要有现成的库,帮我把视频文件解析成一帧一帧的原始画面信息,那么我只需要读取里面的每一个像素的RGB数值,计算出亮度,然后 ...

  7. html5+css3 制作音乐播放器

    //css// body , html{    margin:0;    padding:0;    font:12px Arial, Helvetica, sans-serif;    } .Mus ...

  8. C#调用VlcControl做一个播放器

    开发环境: Visual Studio 2015 .Net Framework 4.5 1.新建一个Windows窗体应用程序 修改框架为.Net Framework 4.5 2.管理NuGet包 下 ...

  9. 让安卓app支持swf的一个播放器,和自己编写的音乐管理程序

    jcenter方式导入 在需要用到这个库的module中的build.gradle中的dependencies中加入 dependencies { compile 'com.yhd.hdswfplay ...

随机推荐

  1. String.Format,DateTime日期时间格式化

    DateTime dt = DateTime.Now;//2010年10月4日 17点05分            string str = "";            //st ...

  2. POJ 3083 Children of the Candy Corn (DFS + BFS + 模拟)

    题目链接:http://poj.org/problem?id=3083 题意: 这里有一个w * h的迷宫,给你入口和出口,让你分别求以下三种情况时,到达出口的步数(总步数包括入口和出口): 第一种: ...

  3. HDU 5695 Gym Class && 百度之星 初赛 1006

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5695 本文链接:http://www.cnblogs.com/Ash-ly/p/5515234.htm ...

  4. Codeforces #430 Div2 D

    #430 Div2 D 题意 给出一些数,每次操作先将所有数异或一个值,再求这些数中没有出现过的最小的非负整数. 分析 对于更新操作,对于 \(x\) 所有为 \(1\) 的位给相应层添加一个标记,当 ...

  5. uva11168

    uva11168 题意 给出一些点坐标,选定一条直线,所有点在直线一侧(或直线上),使得所有点到直线的距离平均值最小. 分析 显然直线一定会经过某两点(或一点),又要求点在直线某一侧,可以直接求出凸包 ...

  6. 洛谷 P1452 Beauty Contest

    题目背景 此处省略1W字^ ^ 题目描述 贝茜在牛的选美比赛中赢得了冠军”牛世界小姐”.因此,贝西会参观N(2 < = N < = 50000)个农场来传播善意.世界将被表示成一个二维平面 ...

  7. 【模拟】bzoj2295 【POJ Challenge】我爱你啊

    #include<cstdio> #include<cstring> using namespace std; int n; char s[100001],table[]=&q ...

  8. [POI2018]Prawnicy

    题目大意: 有$n(n\le10^6)$个线段,每个线段覆盖的范围是$[l_i,r_i]$,要求从中选取$k(k\le10^6)$个线段使得这些线段覆盖范围的交集最大,求最大交集及任意一种方案. 思路 ...

  9. 使用layer.js注意事项

    一.使用时,将layer整个文件夹放置你站点的任何一个目录,layer.js 开发版,layer.min.js 压缩版,引入其中一个即可.css等其它文件无需引入. 二.如果您的js引入是通过合并处理 ...

  10. 【ArcGIS 10.2新特性】ArcGIS 10.2将PostgreSQL原生数据发布为要素服务

    1.ArcGIS 10.2支持原生数据发布为要素服 有没有将自己已有的空间数据发布为要素服务的需求?有没有将非Esri空间数据类型的数据作为服务在Web端展示的需求?     ArcGIS 10.2 ...