转自:
用AVFoundation的AVPlayer做的一个小视频播放器,包含左滑快退,右滑快进,上滑音量加,下滑音量减等基本功能。
支持HLS协议和HTTP协议的视频。
#import "AppDelegate.h" #import "PlayViewController.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { PlayViewController *player=[[PlayViewController alloc]init]; //HLS协议视屏 //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;//调节音量时显示当前音量 @property(nonatomic,strong)UIButton *shareBtn;//分享 //加载指示器 @property(nonatomic,strong)UIActivityIndicatorView *indicator; //判断网络 @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]; } //隐藏状态栏 - (BOOL)prefersStatusBarHidden { return YES; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor=[UIColor blackColor]; //显示网络记载指示器 [self showIndicator]; NSLog(@"\n-->%@\n-->%@",self.urlString,self.videoTitle); //通知中心注册一条监听网络状态的通知 [[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];// 监听status属性 [_item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];// 监听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]; } /** * 评估网络状态 */ -(void)estimateNetworkStatus { FGGNetWorkStatus status=[FGGReachability networkStatus]; if(status==FGGNetWorkStatusNotReachable) { UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"提示" message:@"当前网络不可用" delegate:nil cancelButtonTitle:@"知道了" otherButtonTitles:nil, nil]; [alert show]; } else if(status!=FGGNetWorkStatusWifi) { UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"提示" message:@"非Wifi环境播放视屏会耗费流量!" 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的区别是什么? //快进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]; // 设置进度颜色,已经走过的路径 _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]; } /** * 滑动进度条时执行该方法 * * @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) { }]; } //手势点击屏幕触发的方法 -(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]; } /** * 点击播放按钮时执行的方法 * * @param sender 播放按钮 */ -(void)playAction:(UIButton *)sender { sender.selected=!sender.isSelected; if(_isPlaying) { [_player pause]; } else { [_player play]; } _isPlaying=!_isPlaying; } /** * 收到播放完毕的通知时执行该方法 * * @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]; // 播放状态置为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];// 监听播放状态 } else if ([playerItem status] == AVPlayerStatusFailed) { //移除网络加载指示器 [_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];// 获取缓冲区域 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
作者:CGPointZero
链接:http://www.jianshu.com/p/dad62fecd5df
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。