用AVFoundation的AVPlayer做的一個小視頻播放器,包含左滑快退,右滑快進,上滑音量加,下滑音量減等基本功能。
支持HLS協(xié)議和HTTP協(xié)議的視頻。
#import "AppDelegate.h"
#import "PlayViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication \*)application didFinishLaunchingWithOptions:(NSDictionary \*)launchOptions {
PlayViewController \*player=[[PlayViewController alloc]init];
//HLS協(xié)議視屏
//http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8
//HTTP視頻
//http://www.modrails.com/videos/passenger_nginx.mov
player.urlString=@"http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8";
player.title=@"播放測試";
self.window.rootViewController=player;
return YES;
}
@end
#import "PlayViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import "Tools.h"
#import "FGGReachability.h"
@interface PlayViewController ()
@property(nonatomic,strong)AVPlayer \*player;
@property(nonatomic,strong)AVPlayerItem \*item;
@property(nonatomic,strong)NSString \*totalTime;
@property(nonatomic,assign)BOOL isPlaying;
@property(nonatomic,strong)id timeObserver;
@property(nonatomic,strong)UIProgressView \*progress;
@property(nonatomic,strong)UISlider \*slider; //進度slider
@property(nonatomic,strong)UISlider \*volumeSlider;//音量slider
@property(nonatomic,strong)UIButton \*playBtn;
@property(nonatomic,strong)UILabel \*timeLabel;
@property(nonatomic,strong)id playbackTimeObserver;
//快進
@property(nonatomic,strong)UIImageView \*forward;
//快退
@property(nonatomic,strong)UIImageView \*backward;
@property(nonatomic,strong)UILabel \*volumeLabel;//調節(jié)音量時顯示當前音量
@property(nonatomic,strong)UIButton \*shareBtn;//分享
//加載指示器
@property(nonatomic,strong)UIActivityIndicatorView *indicator;
//判斷網(wǎng)絡
@property(nonatomic,strong)Reachability \*reach;
@end
#define kWidth ([UIScreen mainScreen].bounds.size.width)
#define kHeight ([UIScreen mainScreen].bounds.size.height)
@implementation PlayViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// 隱藏導航欄
self.navigationController.navigationBarHidden=YES;
}
-(void)showIndicator
{
_indicator=[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
_indicator.center=CGPointMake(kHeight/2, kWidth/2);
[self.view addSubview:_indicator];
[_indicator startAnimating];
}
//隱藏狀態(tài)欄
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor=[UIColor blackColor];
//顯示網(wǎng)絡記載指示器
[self showIndicator];
NSLog(@"\n-->%@\n-->%@",self.urlString,self.videoTitle);
//通知中心注冊一條監(jiān)聽網(wǎng)絡狀態(tài)的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(estimateNetworkStatus) name:kReachabilityChangedNotification object:nil];
_reach=[Reachability reachabilityForInternetConnection];
[_reach startNotifier];
[self estimateNetworkStatus];
_item=[[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:self.urlString]];
_player=[[AVPlayer alloc]initWithPlayerItem:_item];
[_item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];// 監(jiān)聽status屬性
[_item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];// 監(jiān)聽loadedTimeRanges屬性
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_item];
AVPlayerLayer \*layer=[AVPlayerLayer playerLayerWithPlayer:_player];
layer.videoGravity=AVLayerVideoGravityResizeAspect;
layer.frame=CGRectMake(0, 0, kHeight, kWidth);
layer.backgroundColor=[UIColor clearColor].CGColor;
[self.view.layer addSublayer:layer];
// 標題
[self createTitleView];
// 界面
[self createUI];
//旋轉視圖
//self.view.transform=CGAffineTransformMakeRotation(3\*M_PI_2);
//layer.transform=CATransform3DMakeRotation(M_PI/2, 0, 0, 1);
self.view.layer.transform=CATransform3DMakeRotation(3\*M_PI_2, 0, 0, 1);
[self addGestures];
}
/\*\*
\* 添加手勢
\*/
-(void)addGestures
{
//添加一個手勢 隱藏與顯示進度條
UITapGestureRecognizer \*tap=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(shiftStatus)];
[self.view addGestureRecognizer:tap];
//增加音量的手勢
UISwipeGestureRecognizer \*increaseGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(increaseVolume:)];
increaseGesture.direction=UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:increaseGesture];
//減小音量的手勢
UISwipeGestureRecognizer \*decreaseGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(decreaseVolume:)];
decreaseGesture.direction=UISwipeGestureRecognizerDirectionDown;
[self.view addGestureRecognizer:decreaseGesture];
//快進
UISwipeGestureRecognizer \*stepForward=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(stepForward10Seconds)];
stepForward.direction=UISwipeGestureRecognizerDirectionRight;
//快退
UISwipeGestureRecognizer \*stepBackward=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(stepBackward10Senconds)];
stepBackward.direction=UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:stepForward];
[self.view addGestureRecognizer:stepBackward];
}
/\*\*
\* 評估網(wǎng)絡狀態(tài)
\*/
-(void)estimateNetworkStatus
{
FGGNetWorkStatus status=[FGGReachability networkStatus];
if(status==FGGNetWorkStatusNotReachable)
{
UIAlertView \*alert=[[UIAlertView alloc]initWithTitle:@"提示"
message:@"當前網(wǎng)絡不可用"
delegate:nil
cancelButtonTitle:@"知道了"
otherButtonTitles:nil, nil];
[alert show];
}
else if(status!=FGGNetWorkStatusWifi)
{
UIAlertView \*alert=[[UIAlertView alloc]initWithTitle:@"提示"
message:@"非Wifi環(huán)境播放視屏會耗費流量!"
delegate:nil
cancelButtonTitle:@"知道了"
otherButtonTitles:nil, nil];
[alert show];
}
}
/\*\*
\* 向上滑動增加音量
\*
\* @param sender 滑動手勢
\*/
-(void)increaseVolume:(UISwipeGestureRecognizer \*)sender
{
if(sender.direction==UISwipeGestureRecognizerDirectionUp)
{
if(_player.volume>=1.0)
return;
_player.volume+=0.1;
// NSLog(@"+++++音量:%f++++++",10\*_player.volume);
__weak PlayViewController \*weakSelf=self;
_volumeLabel.text=[NSString stringWithFormat:@"音量:%d",(int)ceilf(_player.volume\*10)];
[UIView animateWithDuration:0.2 animations:^{
weakSelf.volumeLabel.alpha=1.0;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2 animations:^{
weakSelf.volumeLabel.alpha=0.0;
}];
}];
}
}
/\*\*
\* 向下滑動減小音量
\*
\* @param sender 滑動手勢對象
\*/
-(void)decreaseVolume:(UISwipeGestureRecognizer \*)sender
{
if(sender.direction==UISwipeGestureRecognizerDirectionDown)
{
if(_player.volume<=0.0)
return;
_player.volume-=0.1;
// NSLog(@"------音量:%f------",10\*_player.volume);
__weak PlayViewController \*weakSelf=self;
_volumeLabel.text=[NSString stringWithFormat:@"音量:%d",(int)ceilf(_player.volume\*10)];
[UIView animateWithDuration:0.3 animations:^{
weakSelf.volumeLabel.alpha=1.0;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
weakSelf.volumeLabel.alpha=0.0;
}];
}];
}
}
#pragma mark -
#pragma mark - 快退快進
//@qustion:seekToTime和seekToTime:toleranceBefore:toleranceAfter的區(qū)別是什么?
//快進10秒
-(void)stepForward10Seconds
{
if(_isPlaying)
{
[_item seekToTime:CMTimeMakeWithSeconds(_item.currentTime.value/_item.currentTime.timescale+10, _item.currentTime.timescale) toleranceBefore:CMTimeMake(1, _item.currentTime.timescale) toleranceAfter:CMTimeMake(1, _item.currentTime.timescale)];
__weak typeof(self) weakSelf=self;
[UIView animateWithDuration:0.3 animations:^{
weakSelf.forward.alpha=1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
weakSelf.forward.alpha=0.0;
}];
}];
}
}
//快退10秒
-(void)stepBackward10Senconds
{
if(_isPlaying)
{
[_item seekToTime:CMTimeMakeWithSeconds(_item.currentTime.value/_item.currentTime.timescale-10, _item.currentTime.timescale)];
__weak typeof(self) weakSelf=self;
[UIView animateWithDuration:0.3 animations:^{
weakSelf.backward.alpha=1.0;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
weakSelf.backward.alpha=0.0;
}];
}];
}
}
//界面
-(void)createUI
{
_playBtn=[[UIButton alloc]initWithFrame:CGRectMake(50, 20, 60, 40)];
[_playBtn setTitle:@"Play" forState:UIControlStateNormal];
[_playBtn setTitle:@"Pause" forState:UIControlStateSelected];
[_playBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_playBtn.titleLabel.font=[UIFont boldSystemFontOfSize:16];
[_playBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateSelected];
[_playBtn addTarget:self action:@selector(playAction:) forControlEvents:UIControlEventTouchUpInside];
_playBtn.backgroundColor=[UIColor clearColor];
[self.view addSubview:_playBtn];
_progress=[[UIProgressView alloc]initWithFrame:CGRectMake(110, 41, kHeight-230, 2)];
// 設置軌道顏色,將要走到的路徑的顏色
_progress.trackTintColor=[UIColor whiteColor];
// 設置進度顏色,已經(jīng)走過的路徑
_progress.progressTintColor=[UIColor blueColor];
_slider=[[UISlider alloc]initWithFrame:CGRectMake(110, 26,kHeight-230, 31)];
[_slider setThumbImage:[UIImage imageNamed:@"thumb"] forState:UIControlStateNormal];
_slider.minimumTrackTintColor=[UIColor blueColor];//設置左邊顏色
_slider.maximumTrackTintColor=[UIColor clearColor];//設置右邊顏色
_slider.continuous=NO;
[_slider addTarget:self action:@selector(sliderDidSlided:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:_progress];
[self.view addSubview:_slider];
_timeLabel=[Tools createLabelWithFrame:CGRectMake(kHeight-130, 30, 110, 20) text:nil textColor:[UIColor whiteColor] textAligment:NSTextAlignmentRight andBgColor:[UIColor clearColor] font:[UIFont systemFontOfSize:14]];
_timeLabel.text=@"--:--/--:--";
[self.view addSubview:_timeLabel];
// 音量label
_volumeLabel=[[UILabel alloc]initWithFrame:CGRectMake(kHeight-100, kWidth/2-20, 80, 40)];
_volumeLabel.text=[NSString stringWithFormat:@"音量:%d",(int)ceil(_player.volume)\*10];
_volumeLabel.alpha=0.0;
_volumeLabel.backgroundColor=[UIColor clearColor];
_volumeLabel.font=[UIFont boldSystemFontOfSize:22];
_volumeLabel.textColor=[UIColor whiteColor];
[self.view addSubview:_volumeLabel];
//快進
_forward=[[UIImageView alloc]initWithFrame:CGRectMake(kHeight-164, kWidth/2-32, 64, 64)];
_forward.image=[UIImage imageNamed:@"stepforward"];
[self.view addSubview:_forward];
_forward.alpha=0.0;
//快退
_backward=[[UIImageView alloc]initWithFrame:CGRectMake(100, kWidth/2-32, 64, 64)];
_backward.image=[UIImage imageNamed:@"stepbackward"];
[self.view addSubview:_backward];
_backward.alpha=0.0;
}
//標題視圖
-(void)createTitleView
{
UILabel \*titleLabel=[Tools createLabelWithFrame:CGRectMake(50, 10, kHeight-100, 20) text:self.videoTitle textColor:[UIColor whiteColor] textAligment:NSTextAlignmentCenter andBgColor:[UIColor clearColor] font:[UIFont boldSystemFontOfSize:12]];
[self.view addSubview:titleLabel];
titleLabel.tag=111;
// 返回按鈕
UIButton \*backBtn=[[UIButton alloc]initWithFrame:CGRectMake(10, 20, 40, 40)];
[backBtn setBackgroundImage:[UIImage imageNamed:@"back"] forState:UIControlStateNormal];
[backBtn addTarget:self action:@selector(backAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:backBtn];
}
//返回
-(void)backAction
{
[self.navigationController popViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
/\*\*
\* 滑動進度條時執(zhí)行該方法
\*
\* @param sender slider對象
\*/
-(void)sliderDidSlided:(UISlider \*)sender
{
if(_player.status==AVPlayerStatusReadyToPlay)
{
CGFloat value=sender.value;
[_item seekToTime:CMTimeMake(value, 1)];
}
}
//隱藏上方的條條
-(void)hideStatusLabels
{
__weak PlayViewController \*weakSelf=self;
if(!_isPlaying)
return;
UILabel \*label=(UILabel \*)[self.view viewWithTag:111];
[UIView animateWithDuration:0.5 animations:^{
// 自動播放視頻
[weakSelf.player play];
weakSelf.isPlaying=YES;
label.alpha=0;
weakSelf.timeLabel.alpha=0;
weakSelf.slider.alpha=0;
weakSelf.progress.alpha=0;
weakSelf.playBtn.alpha=0;
} completion:^(BOOL finished) {
}];
}
//顯示上方的條條
-(void)showStatusLabels
{
__weak PlayViewController \*weakSelf=self;
UILabel \*label=(UILabel \*)[self.view viewWithTag:111];
[UIView animateWithDuration:0.5 animations:^{
weakSelf.timeLabel.alpha=1;
weakSelf.slider.alpha=1;
weakSelf.progress.alpha=1;
weakSelf.playBtn.alpha=1;
label.alpha=1;
} completion:^(BOOL finished) {
}];
}
//手勢點擊屏幕觸發(fā)的方法
-(void)shiftStatus
{
if(_playBtn.alpha==0)
{
// 顯示上方條條
[self performSelector:@selector(showStatusLabels) withObject:nil];
// 6秒后 自動隱藏上方條條
[self performSelector:@selector(hideStatusLabels) withObject:nil afterDelay:6];
}
else
// 隱藏條條
[self performSelector:@selector(hideStatusLabels) withObject:nil];
}
/\*\*
\* 點擊播放按鈕時執(zhí)行的方法
\*
\* @param sender 播放按鈕
\*/
-(void)playAction:(UIButton \*)sender
{
sender.selected=!sender.isSelected;
if(_isPlaying)
{
[_player pause];
}
else
{
[_player play];
}
_isPlaying=!_isPlaying;
}
/\*\*
\* 收到播放完畢的通知時執(zhí)行該方法
\*
\* @param notification 通知
\*/
- (void)moviePlayDidEnd:(NSNotification \*)notification
{
NSLog(@"Play end");
[_player seekToTime:kCMTimeZero completionHandler:^(BOOL finished)
{
[self updateVideoSlider:0.0];
[_playBtn setTitle:@"Play" forState:UIControlStateNormal];
}];
}
- (void)observeValueForKeyPath:(NSString \*)keyPath ofObject:(id)object change:(NSDictionary \*)change context:(void \*)context
{
AVPlayerItem \*playerItem = (AVPlayerItem \*)object;
if ([keyPath isEqualToString:@"status"])
{
if ([playerItem status] == AVPlayerStatusReadyToPlay)
{
NSLog(@"AVPlayerStatusReadyToPlay");
// 開始播放視頻
[_player play];
// 播放狀態(tài)置為YES
_isPlaying=YES;
_playBtn.selected=YES;
// 隱藏上方的條條
[self hideStatusLabels];
// 隱藏加載指示器
[_indicator stopAnimating];
[_indicator removeFromSuperview];
CMTime duration = _item.duration;// 獲取視頻總長度
CGFloat totalSecond = playerItem.duration.value / playerItem.duration.timescale;// 轉換成秒
_totalTime = [self convertTime:totalSecond];// 轉換成播放時間
[self customVideoSlider:duration];
NSLog(@"movie total duration:%f",CMTimeGetSeconds(duration));
[self monitoringPlayback:_item];// 監(jiān)聽播放狀態(tài)
}
else if ([playerItem status] == AVPlayerStatusFailed)
{
//移除網(wǎng)絡加載指示器
[_indicator stopAnimating];
[_indicator removeFromSuperview];
//NSLog(@"AVPlayerStatusFailed");
UIAlertView \*alert=[[UIAlertView alloc]initWithTitle:@"提示" message:@"加載失敗" delegate:nil cancelButtonTitle:nil otherButtonTitles:nil, nil];
[alert show];
[alert dismissWithClickedButtonIndex:0 animated:YES];
}
} else if ([keyPath isEqualToString:@"loadedTimeRanges"])
{
NSTimeInterval timeInterval = [self availableDuration];// 計算緩沖進度
// NSLog(@"Time Interval:%f",timeInterval);
CMTime duration = _item.duration;
CGFloat totalDuration = CMTimeGetSeconds(duration);
// 設置進度處理器的進度值
[_progress setProgress:timeInterval / totalDuration animated:YES];
}
}
/\*\*
\* 獲取緩沖總進度
\*
\* @return 緩沖總進度
\*/
- (NSTimeInterval)availableDuration
{
NSArray \*loadedTimeRanges = [[_player currentItem] loadedTimeRanges];
CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 獲取緩沖區(qū)域
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;// 計算緩沖總進度
return result;
}
/\*\*
\* 將視頻播放的當前時間和總時間,格式化顯示在界面上
\*
\* @param second 播放器的當前時間
\*
\* @return 格式化后的顯示時間
\*/
- (NSString \*)convertTime:(CGFloat)second
{
NSDate \*d = [NSDate dateWithTimeIntervalSince1970:second];
NSDateFormatter \*formatter = [[NSDateFormatter alloc] init];
if (second/3600 >= 1)
{
[formatter setDateFormat:@"HH:mm:ss"];
}
else
{
[formatter setDateFormat:@"mm:ss"];
}
NSString \*showtimeNew = [formatter stringFromDate:d];
return showtimeNew;
}
- (void)monitoringPlayback:(AVPlayerItem \*)playerItem
{
__weak PlayViewController \*weakSelf=self;
self.playbackTimeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time)
{
CGFloat currentSecond = playerItem.currentTime.value/playerItem.currentTime.timescale;// 計算當前在第幾秒
[weakSelf updateVideoSlider:currentSecond];
NSString \*timeString = [weakSelf convertTime:currentSecond];
weakSelf.timeLabel.text = [NSString stringWithFormat:@"%@/%@",timeString,weakSelf.totalTime];
}];
}
- (void)customVideoSlider:(CMTime)duration
{
_slider.maximumValue = CMTimeGetSeconds(duration);
UIGraphicsBeginImageContextWithOptions((CGSize){ 1, 1 }, NO, 0.0f);
UIImage \*transparentImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_slider setMinimumTrackImage:transparentImage forState:UIControlStateNormal];
[_slider setMaximumTrackImage:transparentImage forState:UIControlStateNormal];
}
/\*\*
\* 更新滑塊的值
\*
\* @param currentSecond 當前的播放時間
\*/
- (void)updateVideoSlider:(CGFloat)currentSecond
{
[_slider setValue:currentSecond animated:YES];
}
/\*\*
\* 視圖即將消失時移除觀察者對象,注銷通知,如果播放器在播放,讓其暫停,讓播放器指向nil
\*
\* @param animated 動畫效果
\*/
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[_item removeObserver:self forKeyPath:@"status" context:nil];
[_item removeObserver:self forKeyPath:@"loadedTimeRanges" context:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_item];
[_player removeTimeObserver:self.playbackTimeObserver];
if(_isPlaying)
{
[_player pause];
_player=nil;
}
}
@end
附上GitHub代碼地址:AVFoundationPlayer