前言
標(biāo)題必須要浮夸!要感覺(jué)像是一個(gè)大新聞。長(zhǎng)者如是說(shuō)。
其實(shí)是前幾天去面試的時(shí)候,被要求說(shuō)必須做過(guò)視頻播放相關(guān)項(xiàng)目。有點(diǎn)鬧心之余,就花了點(diǎn)時(shí)間在家寫了一個(gè)簡(jiǎn)單播放器,基本實(shí)現(xiàn)了主流播放器的大致功能。之前項(xiàng)目沒(méi)有需求用到過(guò)視頻播放,所以寫的時(shí)候難免會(huì)遇到一些坑,花了一些時(shí)間解決。
蘋果在視頻播放方面提供了多個(gè)框架供我們選擇使用。分別為:
- 基于mediaPlayer類庫(kù)的MPMediaPlayerController(iOS9后遭到廢棄,被AVPlayerViewController所替代)
- 基于AVFounditon類庫(kù)的AVPlayer
- 基于AVKit類庫(kù)的AVPlayerViewController(iOS8后才可使用)
正文
AVPlayer與MPMediaPlayerController比較:
- AVplayer有更多的靈活性,當(dāng)然,也需要你去自定義構(gòu)建UI。還有一大優(yōu)勢(shì),例如其擴(kuò)展的AVQueuePlayer,可以實(shí)現(xiàn)視頻無(wú)縫隊(duì)列播放、多視頻同時(shí)播放、視頻轉(zhuǎn)換、編解碼等功能。
- MPMediaPlayerController實(shí)際上是基于AVPlayer的簡(jiǎn)單UI封裝,對(duì)于一般的播放要求,幾行代碼就可實(shí)現(xiàn),省心省事。
因?yàn)镸PMediaPlayerController是對(duì)AVPlayer進(jìn)行的單例封裝,所以不能進(jìn)行多視頻播放。
播放器Demo(全屏)已實(shí)現(xiàn)功能點(diǎn):
- push到播放器頁(yè)面,橫屏顯示。
- 單機(jī)隱藏or顯示上方標(biāo)題欄與下方操作欄。
- 呼出右側(cè)設(shè)置欄。
- 視頻播放操作與進(jìn)度條設(shè)置。
- 在屏幕上左右拖動(dòng),進(jìn)行視頻快進(jìn)與快退。
- 在屏幕左側(cè)上下拖動(dòng),進(jìn)行亮度調(diào)整。
- 在屏幕右側(cè)上下拖動(dòng),進(jìn)行音量調(diào)整。
想到但是暫未實(shí)現(xiàn)的功能點(diǎn):(大多為優(yōu)化或與業(yè)務(wù)相關(guān))
- 屏幕或進(jìn)度條拖動(dòng)快進(jìn)操作時(shí),添加提示框進(jìn)行快進(jìn)時(shí)間的實(shí)時(shí)提示。
- 用戶無(wú)操作兩三秒之后自動(dòng)隱藏上下View。
- 視頻清晰度調(diào)整按鈕。(更換視頻源)
- 操作加鎖按鈕。(加鎖后未進(jìn)行解鎖操作之前不可進(jìn)行操作)
- 彈幕相關(guān)。
- 用戶允許橫屏狀態(tài)下,橫屏豎屏自動(dòng)進(jìn)行頁(yè)面切換與動(dòng)畫效果等。
- 網(wǎng)絡(luò)視頻的緩存、下載等。
- 軟硬解碼模式切換等。
筆者Demo選擇使用了AVPlayer進(jìn)行視頻播放器的構(gòu)建。由于UI的代碼實(shí)現(xiàn),加上略蛋疼的邏輯代碼,播放器頁(yè)面的代碼量達(dá)到400多行,之后有時(shí)間的話會(huì)再進(jìn)行優(yōu)化。這里只貼出部分代碼,想要查看或借鑒完整Demo,可以到本人github去下載。
使用AVPlayer構(gòu)建播放器
1.導(dǎo)入頭文件
#import <AVFoundation/AVFoundation.h>
2.其實(shí)沒(méi)什么可說(shuō)的,很簡(jiǎn)單,先初始化AVPlayer,然后添加到AVPlayerLayer,最后將其添加到視圖的layer層。
#pragma mark - Demo中此視圖的屬性
#define TopViewHeight 55
#define BottomViewHeight 72
#define mainWidth [UIScreen mainScreen].bounds.size.width
#define mainHeight [UIScreen mainScreen].bounds.size.height
//上層建筑
@property (nonatomic,strong)UIView *topView;
@property (nonatomic,strong)UIButton *backBtn;
@property (nonatomic,strong)UILabel *titleLabel;
@property (nonatomic,strong)UIButton *settingsBtn;
//經(jīng)濟(jì)基礎(chǔ)
@property (nonatomic,strong)UIView *bottomView;
@property (nonatomic,strong)UIButton *playBtn;
@property (nonatomic,strong)UILabel *textLabel;
@property (nonatomic,assign)BOOL isPlay;
@property (nonatomic,strong)UISlider *movieProgressSlider;//進(jìn)度條
@property (nonatomic,assign)CGFloat ProgressBeginToMove;
@property (nonatomic,assign)CGFloat totalMovieDuration;//視頻總時(shí)間
//核心軀干
@property (nonatomic,strong)AVPlayer *player;
//神之右手
@property (nonatomic,strong)UIView *settingsView;
@property (nonatomic,strong)UIView *rightView;
@property (nonatomic,strong)UIButton *setTestBtn;
//touch evens
@property (nonatomic,assign)BOOL isShowView;
@property (nonatomic,assign)BOOL isSettingsViewShow;
@property (nonatomic,assign)BOOL isSlideOrClick;
@property (nonatomic,strong)UISlider *volumeViewSlider;
@property (nonatomic,assign)float systemVolume;//系統(tǒng)音量值
@property (nonatomic,assign)float systemBrightness;//系統(tǒng)亮度
@property (nonatomic,assign)CGPoint startPoint;//起始位置坐標(biāo)
@property (nonatomic,assign)BOOL isTouchBeganLeft;//起始位置方向
@property (nonatomic,copy)NSString *isSlideDirection;//滑動(dòng)方向
@property (nonatomic,assign)float startProgress;//起始進(jìn)度條
#pragma mark - 播放器軀干
- (void)createAvPlayer{
//設(shè)置靜音狀態(tài)也可播放聲音
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
CGRect playerFrame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
AVURLAsset *asset = [AVURLAsset assetWithURL: _url];
Float64 duration = CMTimeGetSeconds(asset.duration);
//獲取視頻總時(shí)長(zhǎng)
_totalMovieDuration = duration;
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset: asset];
_player = [[AVPlayer alloc]initWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
playerLayer.frame = playerFrame;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.view.layer addSublayer:playerLayer];
//需要設(shè)置自動(dòng)播放的直接play即可
//[_player play];
}
屏幕單擊手勢(shì)與視頻快進(jìn)
屏幕單擊
1.符合條件的情況下(手指按下后離開(kāi)屏幕,并且沒(méi)有拖動(dòng))通過(guò)BOOL值判斷,隱藏或顯示上下View
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint point = [[touches anyObject] locationInView:self.view];
if (_isShowView) {
//上下View為顯示狀態(tài),此時(shí)點(diǎn)擊上下View直接return
if ((point.y>CGRectGetMinY(self.topView.frame)&&point.y< CGRectGetMaxY(self.topView.frame))||(point.y<CGRectGetMaxY(self.bottomView.frame)&&point.y>CGRectGetMinY(self.bottomView.frame))) {
return
}
_isShowView = NO;
[UIView animateWithDuration:0.5 animations:^{
_topView.alpha = 0;
_bottomView.alpha = 0;
}];
}else{
_isShowView = YES;
[UIView animateWithDuration:0.5 animations:^{
_topView.alpha = 1;
_bottomView.alpha = 1;
}];
}
}
2.右側(cè)View顯示的狀態(tài)下,點(diǎn)擊屏幕左半空白區(qū)域,隱藏右側(cè)View
if (_isSettingsViewShow) {
if (point.x>CGRectGetMinX(_rightView.frame)&&point.x< CGRectGetMaxX(_rightView.frame)) {
return;
}
_settingsView.alpha = 0;
_isSettingsViewShow = NO;
}
拖動(dòng)快進(jìn)
1.計(jì)算后得出拖動(dòng)方向?yàn)闄M向拖動(dòng)。
CGPoint location = [[touches anyObject] locationInView:self.view];
CGFloat changeY = location.y - _startPoint.y;
CGFloat changeX = location.x - _startPoint.x;
if(fabs(changeX) > fabs(changeY)){
_isSlideDirection = @"橫向";//設(shè)置為橫向
}else if(fabs(changeY)>fabs(changeX)){
_isSlideDirection = @"縱向";//設(shè)置為縱向
}else{
_isSlideOrClick = NO;
NSLog(@"不在五行中。");
}
2.根據(jù)手指按下與離開(kāi)屏幕后,橫向位移的坐標(biāo)值,對(duì)視頻播放進(jìn)度進(jìn)行刷新。
if (_isSlideOrClick) {
_isSlideDirection = @"";
_isSlideOrClick = NO;
CGFloat changeY = point.y - _startPoint.y;
CGFloat changeX = point.x - _startPoint.x;
//如果位置改變 刷新進(jìn)度條
if(fabs(changeX) > fabs(changeY)){
[self scrubberIsScrolling];
}
return;
}
//拖動(dòng)進(jìn)度條
-(void)scrubberIsScrolling{
//計(jì)算出拖動(dòng)的當(dāng)前秒數(shù)(總長(zhǎng)*當(dāng)前百分比)
NSInteger dragedSeconds = floorf(_totalMovieDuration * _movieProgressSlider.value);
CMTime newCMTime = CMTimeMake(dragedSeconds, 1);
[_player seekToTime:newCMTime completionHandler:^(BOOL finished) {
[_player play];
[_playBtn setTitle:@"暫停" forState:UIControlStateNormal];
}];
}
MPMediaPlayerController與AVPlayerViewController的使用介紹
MPMediaPlayerController與AVPlayerViewController,兩者都是基于AVPlayer的簡(jiǎn)單UI封裝,如果只是需要簡(jiǎn)單的視頻播放功能,可以使用這兩個(gè)類快速的構(gòu)建視頻播放器。
MPMediaPlayerController
1.導(dǎo)入頭文件
#import <MediaPlayer/MediaPlayer.h>
2.初始化mp,幾行代碼既可以實(shí)現(xiàn)。
@property (nonatomic,strong)MPMoviePlayerController *mp;
NSURL *url1 = [[NSBundle mainBundle]URLForResource:@"chenyifaer" withExtension:@"mp4"];
_mp = [[MPMoviePlayerController alloc] initWithContentURL:url1];
_mp.controlStyle = MPMovieControlStyleNone;
_mp.view.frame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
[self.view addSubview:_mp.view];
[_mp play];
controlStyle屬性有三個(gè)值:
- MPMovieControlStyleNone, //無(wú)控制
- MPMovieControlStyleEmbedded, //有全屏按鈕與控制
- MPMovieControlStyleFullscreen, // 默認(rèn)全屏,有退出和控制
當(dāng)然還有一些其他屬性,有需要可以自行進(jìn)行設(shè)置。
AVPlayerViewController
1.導(dǎo)入框架與頭文件
#import <AVKit/AVKit.h>
2.初始化AVPlayerViewController,創(chuàng)建一個(gè)AVPlayer添加上。然后將其添加到視圖上,再將View添加到self.View上,然后play即可
NSURL *url1 = [[NSBundle mainBundle]URLForResource:@"chenyifaer" withExtension:@"mp4"];
AVPlayer * player = [AVPlayer playerWithURL:url1];
AVPlayerViewController *playerController = [[AVPlayerViewController alloc]init];
playerController.player = player;
[self addChildViewController:playerController];
[self.view addSubview:playerController.view];
playerController.view.frame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
[player play];
同樣幾行代碼,即可實(shí)現(xiàn)。
音量調(diào)整
1.導(dǎo)入頭文件
#import <MediaPlayer/MediaPlayer.h>
2.借助MPVolumeView類來(lái)獲取到其音量進(jìn)度條,進(jìn)而進(jìn)行音量獲取與控制
@property (nonatomic,strong)UISlider *movieProgressSlider;//進(jìn)度條
MPVolumeView *volumeView = [[MPVolumeView alloc] init];
_volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
_volumeViewSlider = (UISlider *)view;
break;
}
3.觸摸屏幕時(shí),記錄手指按下的位置、獲取按下時(shí)系統(tǒng)的音量(實(shí)現(xiàn)touchesBegan方法)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
_startProgress = _movieProgressSlider.value;
}
4.手指在規(guī)定行為下(手指按下位置為視圖右半?yún)^(qū),且縱向滑動(dòng))持續(xù)滑動(dòng)時(shí),動(dòng)態(tài)改變系統(tǒng)音量(實(shí)現(xiàn)touchesMoved方法)
//手指持續(xù)滑動(dòng),此方法會(huì)持續(xù)調(diào)用
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint location = [[touches anyObject] locationInView:self.view];
int index = location.y - _startPoint.y;
if(index>0){
[_volumeViewSlider setValue:_systemVolume - (abs(index)/10 * 0.05) animated:YES];
[_volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
}else{
[_volumeViewSlider setValue:_systemVolume + (abs(index)/10 * 0.05) animated:YES];
[_volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
}
}
亮度調(diào)整
1.觸摸屏幕時(shí),記錄手指按下的位置、按下時(shí)屏幕的亮度(實(shí)現(xiàn)touchesBegan方法)
2.手指在規(guī)定行為下(手指按下位置為視圖左半?yún)^(qū),且縱向滑動(dòng))持續(xù)滑動(dòng)時(shí),不斷動(dòng)態(tài)處理(實(shí)現(xiàn)touchesMoved方法)
3.改變屏幕亮度:[UIScreen mainScreen].brightness = X (0~1);
//手指持續(xù)滑動(dòng),此方法會(huì)持續(xù)調(diào)用
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint location = [[touches anyObject] locationInView:self.view];
int index = location.y - _startPoint.y;
if(index>0){
[UIScreen mainScreen].brightness = _systemBrightness - abs(index)/10 * 0.01;
}else{
_movieProgressSlider.value = _startProgress - abs(index)/10 * 0.008;
}
}
屏幕旋轉(zhuǎn)
1.設(shè)置應(yīng)用支持橫屏(默認(rèn)支持)。
2.在根視圖中設(shè)置默認(rèn)豎屏(Nav、TabBar、VC基類)
- (BOOL)shouldAutorotate{
return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
3.在需要橫屏的VC中重寫下列方法即可
//允許橫屏旋轉(zhuǎn)
- (BOOL)shouldAutorotate{
return YES;
}
//支持左右旋轉(zhuǎn)
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskLandscapeRight|UIInterfaceOrientationMaskLandscapeLeft;
}
//默認(rèn)為右旋轉(zhuǎn)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationLandscapeRight;
}
源碼
點(diǎn)此下載:github源碼
作者其他文章推薦:iOS 使用CIDetector掃描相冊(cè)二維碼、原生掃描
結(jié)語(yǔ)
怎么樣,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的視頻播放器是不是很簡(jiǎn)單。當(dāng)然,要做一個(gè)好的視頻播放器,需要更多的優(yōu)化處理、UI處理、業(yè)務(wù)功能擴(kuò)展、動(dòng)畫效果....
筆者Demo中關(guān)于timer與進(jìn)度條的互動(dòng)之間不甚完美,還有些小問(wèn)題。
總之,初次嘗試寫,水平有限,如有錯(cuò)誤,還望指正。
參考:
1.http://www.techotopia.com/index.php/iOS8AVPlayerAndPlayerViewController
2.http://stackoverflow.com/questions/8146942/avplayer-and-mpmovieplayercontroller-differences
3.http://www.itdecent.cn/p/e64fe3c7f9ab
4.http://www.th7.cn/Program/IOS/201504/439086.shtml
其他小問(wèn)題多在stackoverflow查詢解決。
PS:最后吐槽一下。因?yàn)閷懙纳晕⒂悬c(diǎn)多,想做個(gè)快速電梯樹進(jìn)行頁(yè)內(nèi)跳轉(zhuǎn),結(jié)果發(fā)現(xiàn)簡(jiǎn)書竟然不支持<span id="jump">正文</span>這樣使用錨點(diǎn)進(jìn)行頁(yè)內(nèi)跳轉(zhuǎn)的語(yǔ)法。簡(jiǎn)直坑爹。