主要内容:
-
iOS
播放器概述 MPMoviePlayerController
MPMoviePlayerViewController
AVPlayer
AVPlayerViewController
一、iOS播放器概述
Apple
我们提供了多种方法来实现视频播放,包括以下:
MPMoviePlayerController
MPMoviePlayerViewController
AVPlayer
-
AVPlayerViewController
;
注意:MPMoviePlayerController
、MPMoviePlayerViewController
在iOS9.0
之后被弃用;
虽说如此,这里还是将所有的用法进行总结以便对比,下面的图示简单概括它们之间的区别:
温馨提示:代码更直观,首先附上本文Demo
二、MPMoviePlayerController
1.播放视频
MPMoviewPlayerController
继承于NSObject
,使用它播放视频需要将其自带的视频View
添加到视图控制器的View
上才能显示视频。使用步骤如下:
步骤1:引用MediaPlayer
框架,声明视图控制器属性PlayerController
#import <mediaplayer mediaplayer.h>
@property(nonatomic,strong)MPMoviePlayerController *playerController;
步骤2:获取视频路径,创建播放器
//本地视频路径
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];
步骤3:设置Frame
将播放器View
添加到视图控制器View
上
self.playerController.view.frame = CGRectMake(0, 10, kDeviceWidth, 300);
[self.view addSubview: self.playerController.view];
步骤4:设置播放器属性
//设置控制面板风格:无,嵌入,全屏,默认
self.playerController.controlStyle = MPMovieControlStyleDefault;
//设置是否自动播放(默认为YES)
self.playerController.shouldAutoplay = NO;
//设置播放器显示模式,类似于图片的处理,设置Fill有可能造成部分区域被裁剪
self.playerController.scalingMode = MPMovieScalingModeAspectFit;
//设置重复模式
self.playerController.repeatMode = MPMovieRepeatModeOne;
步骤5:播放视频
//播放前的准备,会中断当前正在活跃的音频会话
[ self.playerController prepareToPlay];
//播放视频,设置了自动播放之后可以不调用此方法
//[ self.playerController play];
步骤6:在退出界面的时候,关闭播放器,移除通知
- (void)dealloc{
//当前视图控制器pop之后并不会关闭播放,需要手动关闭
[self.playerController stop];
self.playerController = nil;
//移除播放器相关的通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
2.视频播放相关的通知
MPMoviePlayerController
有关视频播放的很多状态控制都是通过通知完成的,尤其是播放在线视频的时候,我们不仅监控视频加载是否成功,也会监控是视频缓存进度等。演示具体代码如下:
步骤1:添加观察者
//关于通知的使用(还有很多通知可以监听,可查看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];
步骤2:添加监听通知的响应方法
//播放状态变化,注意播放完成时的状态是暂停
- (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:添加一个按钮,触发截屏
_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];
步骤2:截取当前屏幕
- (void)captureCurrentScreenImg{
[self.playerController requestThumbnailImagesAtTimes:@[@(self.playerController.currentPlaybackTime)] timeOption:MPMovieTimeOptionNearestKeyFrame];
}
步骤3:监听通知:播放器截屏结束,得到图片并显示截图
- (void)playerCaptureFinished:(NSNotification *)notification{
UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];
self.captureImgView.image = image;
}
三、MPMoviePlayerViewController
MPMovicePlayerViewController
:
- 只能全屏幕播放视频,它是一个包含了
MPMoviePlayerController
类型属性的特殊视图控制器,因此通过模态视图弹出的方式显示视频; - 我们可以通过其
MPMoviePlayerController
属性设置很多播放器的属性,具体用法就和MPMoviePlayerController
相同;
使用该播放视频的具体代码示例如下:
//第一步:获取视频路径
//本地视频
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:引用AVFoundation
框架,添加播放器属性
#import <AVFoundation/AVFoundation.h>
@property (nonatomic,strong)AVPlayer *player;//播放器对象
@property (nonatomic,strong)AVPlayerItem *currentPlayerItem;
步骤2:获取播放地址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];
步骤3:创建播放器(四种方法)
//如果使用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];
步骤4:创建显示视频的AVPlayerLayer
,设置视频显示属性,并添加视频图层
//contentView是一个普通View,用于放置视频视图
/*
AVLayerVideoGravityResizeAspectFill等比例铺满,宽或高有可能出屏幕
AVLayerVideoGravityResizeAspect 等比例 默认
AVLayerVideoGravityResize 完全适应宽高
*/
AVPlayerLayer *avLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
avLayer.videoGravity = AVLayerVideoGravityResizeAspect;
avLayer.frame = _containerView.bounds;
[_containerView.layer addSublayer:avLayer];
步骤5:执行play
方法,开始播放
//本地视频可以直接播放
//网络视频需要监测AVPlayerItem的status属性为AVPlayerStatusReadyToPlay时方法才会生效
[self.player play];
3. 添加属性观察
一个AVPlayerItem
对象对应着一个视频,我们需要通过AVPlayerItem
来获取视频属性。
但是AVPlayerItem
必须是在视频资源加载到可以播放的时候才能使用,这是受限于网络的原因。为了解决这一问题,我们需要使用KVO
监测AVPlayerItem的
status属性,当其AVPlayerItemStatusReadyToPlay
的时候我们才能获取视频相关属性;相关的代码示例如下:
步骤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];
步骤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);
}
}
转换时间格式的方法:
- (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
,其实它是一个如下的结构体:
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:
CMTimeMakeWithSeconds(Flout64 seconds, int32_t scale)
//方法2:
CMTimeMake(int64_t value, int32_t scale)
//注:两者的区别在于方法一的第一个参数可以是float
至于获取视频的总时间在上述代码中已有体现,是在检测播放状态变为AVPlayerStatusReadyToPlay
的时候获取的
//视频总时长,在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`
当播放进度改变的时候方法中的回调会被执行,我们可以在这里做刷新时间的操作,代码示例如下:
__weak __typeof(self) weakSelf = self;
self.timeObserver = [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];
}];
//移除时,调用removeTimeObserver
// [self.player removeTimeObserver:self.timeObserver];
注意:使用addPeriodicTimeObserverForInterval
必须持有返回对象,且在不需要播放器的时候移除此对象; 否则将会导致undefined behavior
,这一点可以从文档是这样说明的:
You must retain this returned value as long as you want the time observer to be invoked by the player.
Pass this object to -removeTimeObserver: to cancel time observation.
Releasing the observer object without a call to -removeTimeObserver: will result in undefined behavior
6. 滑块拖拽修改视频播放进度
//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
中的播放器类,适合开发播放界面要求不是很高的应用; - 相比
AVPlayer
的使用更加方便,而原理上其实还是其中包含了一个AVPlayer
对象; -
iOS9
弃用前两种播放器类后,AVPlayerViewController
更加推荐常用;
AVPlayerViewController
有以下两种播放视频的方式:
1. 直接弹出模态视图控制器播放
//步骤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];
2.添加AVPlayerViewController的View到父视图上播放
使用这种方式播放的优点在于:可以指定播放界面的原始尺寸大小;
但是需要注意: AVPlayerViewController
必须被当前视图控制器所持有,以防止被当做局部变量被释放。为了满足这一条件,有两种方式:
- 可以将
AVPlayerViewController
作为属性; - 也可以使用
addChildViewController
方法将其作为当前视图控制器的子视图控制器;
相关的示例代码如下:
//步骤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
视频播放的基本方法,但这里也仅限一些基础的播放需求。若要实现更为复杂的播放功能,仍然有很多东西需要我们继续深入研究,加油!