iOS视频播放的基本方法

 

本文总结了iOS中最常见的视频播放方法,不同的方法都各具特点,我希望能够总结它们的不同,方便在开发中选择合适的技术方案。

Apple为我们提供了多种方法来实现视频播放,包括MPMoviePlayerController,MPMoviePlayerViewController,AVPlayer,AVPlayerViewController等。而值得注意的是,上述的MPMoviePlayerController与MPMoviePlayerViewController在iOS9.0之后被弃用。虽说如此,这还是将它们的用法总结了一下,下面我们简单来了解一下四种播放方式的区别:

iOS播放视频.png

温馨提示:代码更直观,首先附上本文Demo

一、MPMoviePlayerController

1.播放视频

MPMoviewPlayerController继承于NSObject,使用它播放视频需要将其自带的视频View添加到视图控制器的View上才能显示视频,使用步骤如下:

第一步:引用MediaPlayer框架,声明视图控制器属性PlayerController#import@property(nonatomic,strong)MPMoviePlayerController*playerController;

//第二步:获取视频路径,创建播放器//本地视频路径NSString* localFilePath=[[NSBundlemainBundle]pathForResource:@"不能说的秘密"ofType:@"mp4"];NSURL*localVideoUrl = [NSURLfileURLWithPath:localFilePath];//网络视频路径NSString*webVideoPath =@"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1115.mp4";NSURL*webVideoUrl = [NSURLURLWithString:webVideoPath];self.playerController =[[MPMoviePlayerControlleralloc]initWithContentURL:webVideoUrl];

//第三步:设置Frame将播放器View添加到视图控制器View上self.playerController.view.frame =CGRectMake(0,10, kDeviceWidth,300);[self.view addSubview:self.playerController.view];

//第四步:设置播放器属性//设置控制面板风格:无,嵌入,全屏,默认self.playerController.controlStyle =MPMovieControlStyleDefault;//设置是否自动播放(默认为YES)self.playerController.shouldAutoplay =NO;//设置播放器显示模式,类似于图片的处理,设置Fill有可能造成部分区域被裁剪self.playerController.scalingMode =MPMovieScalingModeAspectFit;//设置重复模式self.playerController.repeatMode =MPMovieRepeatModeOne;

//第五步:播放视频//播放前的准备,会中断当前正在活跃的音频会话[self.playerController  prepareToPlay];//播放视频,设置了自动播放之后可以不调用此方法//[ self.playerController  play];

//第六步:在退出界面的时候,关闭播放器,移除通知- (void)dealloc{//当前视图控制器pop之后并不会关闭播放,需要手动关闭[self.playerController stop];self.playerController =nil;//移除播放器相关的通知[[NSNotificationCenterdefaultCenter] removeObserver:self];}

2.视频播放相关的通知

MPMoviePlayerController有关视频播放的很多状态控制都是通过通知完成的,尤其是播放在线视频的时候,我们不仅监控视频加载是否成功,也会监控是视频缓存进度等。这里演示一些常用的通知如下:

//关于通知的使用(还有很多通知可以监听,可查看SDK)NSNotificationCenter*notificaionCenter = [NSNotificationCenterdefaultCenter];//监听播放器状态的变化[notificaionCenter addObserver:selfselector:@selector(playerStateChanged:)                              name:MPMoviePlayerPlaybackStateDidChangeNotificationobject:nil];//监听播放完成[notificaionCenter addObserver:selfselector:@selector(playerFinished) name:MPMoviePlayerPlaybackDidFinishNotificationobject:nil];//监听切换到全屏[notificaionCenter addObserver:selfselector:@selector(palyerChangeFullScreen) name:MPMoviePlayerDidEnterFullscreenNotificationobject:nil];//监听截屏操作完成[notificaionCenter addObserver:selfselector:@selector(playerCaptureFinished:) name:MPMoviePlayerThumbnailImageRequestDidFinishNotificationobject:nil];

#pragma mark - 监听通知的响应方法//播放状态变化,注意播放完成时的状态是暂停- (void)playerStateChanged:(NSNotification*)notificaion{switch(self.playerController.playbackState) {caseMPMoviePlaybackStateStopped:{NSLog(@"播放停止");break;        }caseMPMoviePlaybackStatePlaying:{NSLog(@"播放器正在播放");break;        }caseMPMoviePlaybackStatePaused:{NSLog(@"播放器暂停");break;        }caseMPMoviePlaybackStateInterrupted:{NSLog(@"播放器中断");break;        }caseMPMoviePlaybackStateSeekingForward:{NSLog(@"播放器快进");break;        }caseMPMoviePlaybackStateSeekingBackward:{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.实现截屏

//添加一个按钮,点击开始截屏_captureBtn = [[UIButtonalloc] initWithFrame:CGRectMake(30,CGRectGetMaxY(self.playerController.view.frame) +30, kDeviceWidth -30*2,50)];_captureBtn.backgroundColor = [UIColorpurpleColor];[_captureBtn setTitle:@"截图当前屏幕"forState:UIControlStateNormal];[_captureBtn addTarget:selfaction:@selector(captureCurrentScreenImg) forControlEvents:UIControlEventTouchUpInside];[self.view addSubview:_captureBtn];//添加一个ImgView 显示截屏后的图片_captureImgView = [[UIImageViewalloc] initWithFrame:CGRectMake((kDeviceWidth -150)/2,CGRectGetMaxY(_captureBtn.frame) +20,150,150)]; _captureImgView.contentMode =UIViewContentModeScaleAspectFit;_captureImgView.backgroundColor = [UIColorgrayColor];[self.view addSubview:_captureImgView];

//截取当前屏幕- (void)captureCurrentScreenImg{    [self.playerController requestThumbnailImagesAtTimes:@[@(self.playerController.currentPlaybackTime)] timeOption:MPMovieTimeOptionNearestKeyFrame];}

//监听通知:播放器截屏结束,得到图片并显示截图- (void)playerCaptureFinished:(NSNotification*)notification{UIImage*image=notification.userInfo[MPMoviePlayerThumbnailImageKey];self.captureImgView.image = image;}

二、MPMoviePlayerViewController

MPMovicePlayerViewControlle只能全屏幕播放视频,它是一个包含了MPMoviePlayerController类型属性的特殊视图控制器,因此它是通过模态视图弹出的方式显示视频的。理解了这个,我们就可以知道在使用MPMovicePlayerViewController的时候我们可以通过它的MPMoviePlayerController属性设置很多播放器的属性了,具体用法和MPMoviePlayerController相同,就不过多的解释了,播放视频的代码示例如下;

//第一步:获取视频路径//本地视频NSString* localFilePath=[[NSBundlemainBundle]pathForResource:@"不能说的秘密"ofType:@"mp4"];NSURL*localVideoUrl = [NSURLfileURLWithPath:localFilePath];//在线视频//NSString *webVideoPath = @"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1115.mp4";//NSURL *webVideoUrl = [NSURL URLWithString:webVideoPath];//第二步:创建视频播放器MPMoviePlayerViewController*playerViewController = [[MPMoviePlayerViewControlleralloc] initWithContentURL:localVideoUrl];//第三步:设置播放器属性//通过moviePlayer属性设置播放器属性(与MPMoviePlayerController类似)playerViewController.moviePlayer.scalingMode =MPMovieScalingModeFill;//第四步:跳转视频播放界面[selfpresentViewController:playerViewController animated:YEScompletion:nil];

三、AVPlayer

AVPlayer相比上述两种方式,播放视频功能更加强大,使用也十分灵活,因为它更加接近底层。但是AVPlayer本身是不能直接显示视频的,必须创建一个播放层AVPlayerLayer并将其添加到其他的视图Layer上才能显示。

1. 使用AVPlayer需要了解的常用类

AVAsset:一个用于获取多媒体信息的抽象类,但不能直接使用

AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象

AVPlayerItem:一个媒体资源管理对象,用于管理视频的基本信息和状态,一个AVPlayerItem对应一个视频资源

AVPlayer:负责视频播放、暂停、时间控制等操作

AVPlayerLayer:负责显示视频的图层,如果不设置此属性,视频就只有声音没有图像

2. AVPlayer的使用步骤

//第一步:引用AVFoundation框架,添加播放器属性#import<AVFoundation/AVFoundation.h>@property(nonatomic,strong)AVPlayer*player;//播放器对象@property(nonatomic,strong)AVPlayerItem*currentPlayerItem;

//第二步:获取播放地址URL//本地视频路径NSString* localFilePath=[[NSBundlemainBundle]pathForResource:@"不能说的秘密"ofType:@"mp4"];NSURL*localVideoUrl = [NSURLfileURLWithPath:localFilePath];//网络视频路径NSString*webVideoPath =@"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1129.mp4";NSURL*webVideoUrl = [NSURLURLWithString:webVideoPath];

//第三步:创建播放器(四种方法)//如果使用URL创建的方式会默认为AVPlayer创建一个AVPlayerItem//self.player = [AVPlayer playerWithURL:localVideoUrl];//self.player = [[AVPlayer alloc] initWithURL:localVideoUrl];//self.player = [AVPlayer playerWithPlayerItem:playerItem];AVPlayerItem*playerItem = [[AVPlayerItemalloc] initWithURL:webVideoUrl];self.currentPlayerItem = playerItem;self.player = [[AVPlayeralloc] initWithPlayerItem:playerItem];

//第四步:创建显示视频的AVPlayerLayer,设置视频显示属性,并添加视频图层//contentView是一个普通View,用于放置视频视图/*

  AVLayerVideoGravityResizeAspectFill等比例铺满,宽或高有可能出屏幕

  AVLayerVideoGravityResizeAspect 等比例  默认

  AVLayerVideoGravityResize 完全适应宽高

*/AVPlayerLayer*avLayer = [AVPlayerLayerplayerLayerWithPlayer:self.player];avLayer.videoGravity =AVLayerVideoGravityResizeAspect;avLayer.frame = _containerView.bounds;[_containerView.layer addSublayer:avLayer];

//第六步:执行play方法,开始播放//本地视频可以直接播放//网络视频需要监测AVPlayerItem的status属性为AVPlayerStatusReadyToPlay时方法才会生效[self.player play];

3. 添加属性观察

一个AVPlayerItem对象对应着一个视频,我们需要通过AVPlayerItem来获取视频属性。但是AVPlayerItem必须是在视频资源加载到可以播放的时候才能使用,这是受限于网络的原因。解决这一问题,我们需要使用KVO监测AVPlayerItem的status属性,当其为AVPlayerItemStatusReadyToPlay的时候我们才能获取视频相关属性。相关的代码示例如下:

//1.注册观察者,监测播放器属性//观察Status属性,可以在加载成功之后得到视频的长度[self.player.currentItem addObserver:selfforKeyPath:@"status"options:NSKeyValueObservingOptionNewcontext:nil];//观察loadedTimeRanges,可以获取缓存进度,实现缓冲进度条[self.player.currentItem addObserver:selfforKeyPath:@"loadedTimeRanges"options:NSKeyValueObservingOptionNewcontext: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属性最新的状态AVPlayerStatusstatus = [[change objectForKey:@"new"] intValue];switch(status) {caseAVPlayerStatusReadyToPlay:{//获取视频长度CMTimeduration = playerItem.duration;//更新显示:视频总时长(自定义方法显示时间的格式)self.totalNeedPlayTimeLabel.text = [selfformatTimeWithTimeInterVal:CMTimeGetSeconds(duration)];//开启滑块的滑动功能self.sliderView.enabled =YES;//关闭加载Loading提示[selfshowaAtivityInDicatorView:NO];//开始播放视频[self.player play];break;            }caseAVPlayerStatusFailed:{//视频加载失败,点击重新加载[selfshowaAtivityInDicatorView:NO];//关闭Loading视图self.playerInfoButton.hidden =NO;//显示错误提示按钮,点击后重新加载视频[self.playerInfoButton setTitle:@"资源加载失败,点击继续尝试加载"forState:UIControlStateNormal];break;            }caseAVPlayerStatusUnknown:{NSLog(@"加载遇到未知问题:AVPlayerStatusUnknown");break;            }default:break;        }    }elseif([keyPath isEqualToString:@"loadedTimeRanges"]) {//获取视频缓冲进度数组,这些缓冲的数组可能不是连续的NSArray*loadedTimeRanges = playerItem.loadedTimeRanges;//获取最新的缓冲区间CMTimeRangetimeRange = [loadedTimeRanges.firstObjectCMTimeRangeValue];//缓冲区间的开始的时间NSTimeIntervalloadStartSeconds =CMTimeGetSeconds(timeRange.start);//缓冲区间的时长NSTimeIntervalloadDurationSeconds =CMTimeGetSeconds(timeRange.duration);//当前视频缓冲时间总长度NSTimeIntervalcurrentLoadTotalTime = loadStartSeconds + loadDurationSeconds;//NSLog(@"开始缓冲:%f,缓冲时长:%f,总时间:%f", loadStartSeconds, loadDurationSeconds, currentLoadTotalTime);//更新显示:当前缓冲总时长_currentLoadTimeLabel.text = [selfformatTimeWithTimeInterVal:currentLoadTotalTime];//更新显示:视频的总时长_totalNeedLoadTimeLabel.text = [selfformatTimeWithTimeInterVal:CMTimeGetSeconds(self.player.currentItem.duration)];//更新显示:缓冲进度条的值_progressView.progress = currentLoadTotalTime/CMTimeGetSeconds(self.player.currentItem.duration);    }}

//转换时间格式的方法- (NSString*)formatTimeWithTimeInterVal:(NSTimeInterval)timeInterVal{intminute =0, hour =0, secend = timeInterVal;    minute = (secend %3600)/60;    hour = secend /3600;    secend = secend %60;return[NSStringstringWithFormat:@"%02d:%02d:%02d", hour, minute, secend];}

4. 获取当前播放时间与总时间

在此之前我们需要首先了解一个数据类型,也就是上述操作中的CMTime, 在AVPlayer的使用中我们会经常用到它,其实CMTime是一个结构体如下:

typedefstruct{CMTimeValuevalue;// 帧数CMTimeScaletimescale;// 帧率(影片每秒有几帧)CMTimeFlagsflags;CMTimeEpochepoch;        }CMTi

在上面的操作中我们看到AVPlayerItem的Duration属性就是一个CMTime类型的数据。所以获取视频的总时长(秒)需要duration.value/duration.timeScale。当然系统也为我们提供了CMTimeGetSeconds函数更加方便计算:

总时长: duration.value ==CMTimeGetSeconds(duration) 。

在快进视频到某一个位置的时候我们也需要创建CMTime作为参数,那么CMTime的创建方法有两种:

//方法1:CMTimeMakeWithSeconds(Flout64 seconds,int32_tscale)//方法2:CMTimeMake(int64_tvalue,int32_tscale)//注:两者的区别在于方法一的第一个参数可以是float

至于获取视频的总时间在上述代码中已有体现,是在检测播放状态变为AVPlayerStatusReadyToPlay的时候获取的

//视频总时长,在AVPlayerItem状态为AVPlayerStatusReadyToPlay时获取CMTimeduration =self.player.currentItem.duration;CGFloattotalTime =CMTimeGetSeconds(duration);//当前AVPlayer的播放时长CMTimecmTime =self.player.currentTime;CGFloatcurrentTime  =CMTimeGetSeconds(cmTime);

5. 播放进度与状态的刷新

实时更新当前播放时间,这时候我们不必使用定时器,因为AVPlayer已经提供了方法:

addPeriodicTimeObserverForInterval: queue: usingBlock。当播放进度改变的时候方法中的回调会被执行。我们可以在这里做刷新时间的操作,代码示例如下:

__weak__typeof(self) weakSelf =self;[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1,1) queue:dispatch_get_main_queue() usingBlock:^(CMTimetime) {//当前播放的时间NSTimeIntervalcurrentTime =CMTimeGetSeconds(time);//视频的总时间NSTimeIntervaltotalTime =CMTimeGetSeconds(weakSelf.player.currentItem.duration);//设置滑块的当前进度weakSelf.sliderView.value = currentTime/totalTime;//设置显示的时间:以00:00:00的格式weakSelf.currentTimeLabel.text = [weakSelf formatTimeWithTimeInterVal:currentTime];    }];

6. 滑块拖拽修改视频播放进度

//UISlider的响应方法:拖动滑块,改变播放进度- (IBAction)sliderViewChange:(id)sender {if(self.player.status ==AVPlayerStatusReadyToPlay){NSTimeIntervalplayTime =self.sliderView.value *CMTimeGetSeconds(self.player.currentItem.duration);CMTimeseekTime =CMTimeMake(playTime,1);        [self.player seekToTime:seekTime completionHandler:^(BOOLfinished) {        }];    }}

四、AVPlayerViewController

AVPlayerViewController是iOS8新增视频框架AVKit中的一个播放器类。由于iOS9弃用前两种播放器类的原因,AVPlayerViewController也将变得更加常用。AVPlayerViewController适合开发播放界面要求不是很高的应用。其相比AVPlayer的使用更加方便,但是原理上还是AVPlayerViewController包含了一个AVPlayer对象。

AVPlayerViewController有两种播放视频的方式:

第一种:直接弹出模态视图控制器播放

//步骤1:获取视频路径NSString*webVideoPath =@"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1213.mp4";NSURL*webVideoUrl = [NSURLURLWithString:webVideoPath];//步骤2:创建AVPlayerAVPlayer*avPlayer = [[AVPlayeralloc] initWithURL:webVideoUrl];//步骤3:使用AVPlayer创建AVPlayerViewController,并跳转播放界面AVPlayerViewController*avPlayerVC =[[AVPlayerViewControlleralloc] init];avPlayerVC.player = avPlayer;[selfpresentViewController:avPlayerVC animated:YEScompletion:nil];

第二种:添加AVPlayerViewController的View到父视图上播放。

使用这种方式播放的优点在于可以指定播放界面的原始尺寸大小,但是值得注意的是AVPlayerViewController必须被当前视图控制器所持有,以防止被当做局部变量被释放。为了满足这一条件,我们可以将AVPlayerViewController作为属性,也可以使用addChildViewController方法将其作为当前视图控制器的子视图控制器,示例代码如下:

//步骤1:获取视频路径NSString*webVideoPath =@"http://api.junqingguanchashi.net/yunpan/bd/c.php?vid=/junqing/1213.mp4";NSURL*webVideoUrl = [NSURLURLWithString:webVideoPath];//步骤2:创建AVPlayerAVPlayer*avPlayer = [[AVPlayeralloc] initWithURL:webVideoUrl];//步骤3:使用AVPlayer创建AVPlayerViewController,并跳转播放界面AVPlayerViewController*avPlayerVC =[[AVPlayerViewControlleralloc] init];avPlayerVC.player = avPlayer;//步骤4:设置播放器视图大小avPlayerVC.view.frame =CGRectMake(25,0,320,300);//特别注意:AVPlayerViewController不能作为局部变量被释放,否则无法播放成功//解决1.AVPlayerViewController作为属性//解决2:使用addChildViewController,AVPlayerViewController作为子视图控制器[selfaddChildViewController:avPlayerVC];[self.view addSubview:avPlayerVC.view];

最后总结:

以上就是iOS视频播放的基本方法,但这里也仅限一些基础的播放需求。若要实现更为复杂的播放功能,仍然有很多东西需要我们继续深入研究,加油!

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

推荐阅读更多精彩内容