美文网首页app开发ios实用开发技巧
iOS AVPlayer自定义播放器

iOS AVPlayer自定义播放器

作者: 像风一样的孩子丶 | 来源:发表于2017-05-12 15:28 被阅读67次

    最近通过Avplayer自定义了一个视频播放器.

    项目框架#import <AVFoundation/AVFoundation.h>

    1.首先播放网络视频.我需要获取一个帧作为视频简介图片,还需要获取视频时长来作为视频的简介.

    IMG_1047.PNG
    这个时候我们需要AVURLAsset帮助获取.

    由于AVURLAsset是获取数据是同步的,有时候会导致页面卡顿,所以需要将上方法放到子线程.获取完成在主线程更新视图.

        //子线程获取数据
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [weakSelf getThumbnailImageAndTotalTimeWithURL:weakSelf.videoURL];
        });
    
    - (void)getThumbnailImageAndTotalTimeWithURL:(NSString *)videoURL {
        NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
        AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:videoURL] options:opts];
        
        //获取image
        AVAssetImageGenerator *assetImageGenerator =[[AVAssetImageGenerator alloc] initWithAsset:urlAsset];
        assetImageGenerator.appliesPreferredTrackTransform = YES;
        assetImageGenerator.maximumSize = CGSizeMake(self.videoImageView.frame.size.width, self.videoImageView.frame.size.height);
        NSError *error = nil;
        //获取第一秒的帧
        CGImageRef imgRef = [assetImageGenerator copyCGImageAtTime:CMTimeMake(1.0, 1.0) actualTime:NULL error:&error];
        UIImage *image = [UIImage imageWithCGImage:imgRef];
        
        //获取时长
        self.totalTime = (CGFloat)(urlAsset.duration.value / urlAsset.duration.timescale);
        
        //需要在主线程进行UI更新
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [weakSelf.videoImageView setImage:image];
            weakSelf.totalTimeL.text = [self getVideoLengthFromTimeLength:weakSelf.totalTime];
        });
        
    }
    

    2.播放器


    IMG_1049.PNG

    只是实现了一些基本的功能.大牛们就别喷我啦.

    .h

    #import <UIKit/UIKit.h>
    #import <AVFoundation/AVFoundation.h>
    
    @protocol VideoPlayerViewControllerDelegate <NSObject>
    //点击返回的回调 time:观看时间   isWatchFinish:是否观看完成
    - (void)moviePlayerVCCallbackWithTime:(CMTime)time isWatchFinish:(BOOL)isWatchFinish;
    @end
    
    @interface VideoPlayerViewController : UIViewController
    @property (nonatomic, assign) id <VideoPlayerViewControllerDelegate> delegate;
    @property (nonatomic, assign) CMTime watchCMTime;//观看时间,外部传入
    @property (nonatomic, strong) NSURL *url;
    @property (nonatomic, copy) NSString *titleStr;//title
    @end
    

    .m

    #define TopViewHeight 50
    #define BottomViewHeight 50
    #define ViewWidth [UIScreen mainScreen].bounds.size.width
    #define ViewHeight [UIScreen mainScreen].bounds.size.height
    
    #import "VideoPlayerViewController.h"
    
    @interface VideoPlayerViewController ()
    //TopView
    @property (nonatomic, strong) UIView *topView;
    @property (nonatomic, strong) UIButton *backBtn;
    @property (nonatomic, strong) UILabel *titleL;
    //BottomView
    @property (nonatomic, strong) UIView *bottomView;
    @property (nonatomic, strong) UIButton *playBtn;
    @property (nonatomic, strong) UILabel *timeL;
    @property (nonatomic, strong) UISlider *videoSlider;
    
    //Player
    @property (nonatomic, strong) AVPlayer *player;
    @property (nonatomic, strong) AVPlayerItem *playerItem;
    //播放数据
    @property (nonatomic, assign) CGFloat totalTime;
    @property (nonatomic, assign) CMTime currentTime;
    @property (nonatomic, assign) id timeObser;
    @property (nonatomic, strong) NSTimer *avTimer;
    
    //判断状态
    @property (nonatomic, assign) BOOL isPlay;//是否播放
    @property (nonatomic, assign) BOOL isShowToolView;//是否展示工具
    @property (nonatomic, assign) BOOL isMoveSlider;//是否移动滑竿
    @property (nonatomic, assign) BOOL isWatchFinish;//是否观看结束
    @end
    
    @implementation VideoPlayerViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self prefersStatusBarHidden];
        self.isShowToolView = YES;
        self.isWatchFinish = NO;
        self.view.backgroundColor = [UIColor blackColor];
        [self layoutAVPlayer];
        [self layoutTopView];
        [self layoutBottomView];
        if (0.0 != self.watchCMTime.value && 0.0 != self.watchCMTime.timescale) {
            //已为您跳转上次播放
            [self.player seekToTime:self.watchCMTime];
        }
        [self playerSetPlay:YES];
        //触发计时
        [self.avTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5]];
    }
    
    #pragma mark - AVPlayer
    - (void)layoutAVPlayer {
        CGRect playerFrame = CGRectMake(0, 0, self.view.layer.bounds.size.height, self.view.layer.bounds.size.width);
        
        AVURLAsset *asset = [AVURLAsset assetWithURL: _url];
        //获取视频总时长
        _totalTime = (CGFloat)(asset.duration.value / asset.duration.timescale);
        _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];
        
        //Observer
        [self addVideoTimerObserver];
        [self addNotifications];
    }
    
    #pragma mark - TopView
    - (void)layoutTopView {
        self.topView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.height, TopViewHeight)];
        _topView.backgroundColor = [UIColor blackColor];
        _topView.alpha = 0.6;
        
        self.backBtn = [[UIButton alloc]initWithFrame:CGRectMake(20, 10, 30, 30)];
        [_backBtn setImage:[UIImage imageNamed:@"player_back"] forState:UIControlStateNormal];
        [_backBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [_backBtn addTarget:self action:@selector(backClick) forControlEvents:UIControlEventTouchUpInside];
        [_topView addSubview:_backBtn];
        
        self.titleL = [[UILabel alloc]initWithFrame:CGRectMake(70, 10, self.view.bounds.size.width - 140, 30)];
        _titleL.font = [UIFont fontWithName:@"STHeitiSC-Light" size:20];
        _titleL.backgroundColor = [UIColor clearColor];
        _titleL.text = self.titleStr;
        _titleL.textColor = [UIColor whiteColor];
        _titleL.textAlignment = NSTextAlignmentCenter;
        [_topView addSubview:_titleL];
        
        [self.view addSubview:_topView];
    }
    
    //返回Click
    - (void)backClick {
        [self.player pause];
        if ([self.delegate respondsToSelector:@selector(moviePlayerVCCallbackWithTime:isWatchFinish:)]) {
            [self.delegate moviePlayerVCCallbackWithTime:self.currentTime isWatchFinish:self.isWatchFinish];
        }
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    
    //设置Click
    - (void)settingsClick:(UIButton *)btn {
        _isShowToolView = NO;
        [UIView animateWithDuration:0.5 animations:^{
            _topView.hidden = YES;
            _bottomView.hidden = YES;
            _videoSlider.hidden = YES;
        }];
    }
    
    #pragma mark - BottomView
    - (void)layoutBottomView {
        _bottomView = [[UIView alloc]initWithFrame:CGRectMake(0, ViewWidth - BottomViewHeight, ViewHeight, BottomViewHeight)];
        _bottomView.backgroundColor = [UIColor blackColor];
        _bottomView.alpha = 0.6;
        [self.view addSubview:_bottomView];
        
        _playBtn = [[UIButton alloc]initWithFrame:CGRectMake(10, 10, 30, 30)];
        [_playBtn setImage:[UIImage imageNamed:@"player_play"] forState:UIControlStateNormal];
        [_playBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [_playBtn addTarget:self action:@selector(playBtnClick:) forControlEvents:UIControlEventTouchUpInside];
        [_bottomView addSubview:_playBtn];
        
        _timeL = [[UILabel alloc]initWithFrame:CGRectMake(50, 10, self.view.bounds.size.height - 70, 30)];
        _timeL.backgroundColor = [UIColor clearColor];
        _timeL.textColor = [UIColor whiteColor];
        _timeL.textAlignment = NSTextAlignmentRight;
        [_bottomView addSubview:_timeL];
        //在totalTimeLabel上显示总时间
        _timeL.text = [NSString stringWithFormat:@"00:00/%@", [self getVideoLengthFromTimeLength:_totalTime]];
        
        //进度条
        self.videoSlider = [[UISlider alloc]initWithFrame:CGRectMake(0, _bottomView.frame.origin.y - 10, _bottomView.frame.size.width, 20)];
        [_videoSlider setMinimumTrackTintColor:[UIColor whiteColor]];
        [_videoSlider setMaximumTrackTintColor:[UIColor colorWithRed:0.49f green:0.48f blue:0.49f alpha:1.00f]];
        [_videoSlider setThumbImage:[UIImage imageNamed:@"video_slider_progressbar"] forState:UIControlStateNormal];
        [_videoSlider addTarget:self action:@selector(scrubbingDidBegin) forControlEvents:UIControlEventTouchDown];
        [_videoSlider addTarget:self action:@selector(sliderValueChanged) forControlEvents:UIControlEventValueChanged];
        [_videoSlider addTarget:self action:@selector(scrubbingDidEnd) forControlEvents:(UIControlEventTouchUpInside | UIControlEventTouchCancel)];
        [self.view addSubview:_videoSlider];
    }
    
    //按住滑块
    - (void)scrubbingDidBegin {
        //暂停播放
        [self playerSetPlay:NO];
        //停止计时
        [self.avTimer setFireDate:[NSDate distantFuture]];
        self.isMoveSlider = YES;
    }
    
    //滑动值变化
    - (void)sliderValueChanged {
        //设置滑动的时间
        float dragedSeconds = floorf(self.totalTime * _videoSlider.value);
        CMTime newCMTime = CMTimeMake(dragedSeconds, 1.0);
        self.timeL.text = [NSString stringWithFormat:@"%@/%@", [self getStringFromCMTime:newCMTime], [self getVideoLengthFromTimeLength:self.totalTime]];
    }
    
    //释放滑块
    - (void)scrubbingDidEnd {
        //1.通过实际百分比获取秒数。
        float dragedSeconds = floorf(self.totalTime * _videoSlider.value);
        CMTime newCMTime = CMTimeMake(dragedSeconds, 1.0);
        //2.更新电影到实际秒数。
        [_player seekToTime:newCMTime];
        //3.重新开始播放
        [self playerSetPlay:YES];
        
        //重新开始计时
        [self.avTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5]];
        self.isMoveSlider = NO;
    }
    
    //播放/暂停
    - (void)playBtnClick:(UIButton *)btn{
        if (!_isPlay) {
            [self playerSetPlay:YES];
        } else {
            [self playerSetPlay:NO];
        }
    }
    
    #pragma mark - Set Play
    - (void)playerSetPlay:(BOOL)isPlay{
        if (isPlay) {
            [_player play];
            _isPlay = YES;
            [_playBtn setImage:[UIImage imageNamed:@"player_pause"] forState:UIControlStateNormal];
        } else {
            [_player pause];
            _isPlay = NO;
            [_playBtn setImage:[UIImage imageNamed:@"player_play"] forState:UIControlStateNormal];
        }
    }
    
    #pragma mark - Touch
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
        CGPoint point = [[touches anyObject] locationInView:self.view];
        if (_isShowToolView) {
            //上下View为显示状态,此时点击上下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;
            }
            _isShowToolView = NO;
            [UIView animateWithDuration:0.5 animations:^{
                _topView.hidden = YES;
                _bottomView.hidden = YES;
                _videoSlider.hidden = YES;
            }];
            //上下视图消失,关闭计时
            [self.avTimer setFireDate:[NSDate distantFuture]];
        } else {
            _isShowToolView = YES;
            [UIView animateWithDuration:0.5 animations:^{
                _topView.hidden = NO;
                _bottomView.hidden = NO;
                _videoSlider.hidden = NO;
            }];
            //上下视图出现,开启计时
            [self.avTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5]];
        }
    }
    
    #pragma mark - Timer
    //计时 5秒Hide Bar
    - (NSTimer *)avTimer {
        if (!_avTimer) {
            self.avTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(handleHideBar) userInfo:nil repeats:YES];
        }
        return _avTimer;
    }
    - (void)handleHideBar {
        _isShowToolView = NO;
        [UIView animateWithDuration:0.5 animations:^{
            _topView.hidden = YES;
            _bottomView.hidden = YES;
            _videoSlider.hidden = YES;
        }];
        [self.avTimer setFireDate:[NSDate distantFuture]];
    }
    
    
    #pragma mark - 状态栏与横屏设置
    //隐藏状态栏
    - (BOOL)prefersStatusBarHidden{
        return YES;
    }
    //默认为右旋转
    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
        return UIInterfaceOrientationLandscapeRight;
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    
    #pragma mark - TimerObserver
    //检测观看时间
    - (void)addVideoTimerObserver {
        __weak typeof(self) weakSelf = self;
        _timeObser = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:NULL usingBlock:^(CMTime time) {
            weakSelf.currentTime = time;
            if (!weakSelf.isMoveSlider) {
                weakSelf.timeL.text = [NSString stringWithFormat:@"%@/%@", [weakSelf getStringFromCMTime:time], [weakSelf getVideoLengthFromTimeLength:weakSelf.totalTime]];
                CGFloat watchTime = time.value / time.timescale;
                CGFloat progress = watchTime / self.totalTime * 1.0f;
                [weakSelf.videoSlider setValue:progress animated:NO];
            }
        }];
    }
    - (void)removeVideoTimerObserver {
        [_player removeTimeObserver:_timeObser];
    }
    
    #pragma mark - Notifications
    - (void)addNotifications {
        /*
         *  Video
         */
        //监听status属性
        [self.playerItem addObserver:self forKeyPath:@"status"options:NSKeyValueObservingOptionNew context:nil];
        //视频播放完成
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
        
        /*
         *  Application
         */
        //进入后台
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationWillResignActiveNotification object:nil];
        //进入前台
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayground) name:UIApplicationDidBecomeActiveNotification object:nil];
        //监听耳机状态
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:UIApplicationDidBecomeActiveNotification object:nil];
    }
    - (void)removeNotifications {
        [self.playerItem removeObserver:self forKeyPath:@"status"];
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    
    #pragma mark - KVO
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
        if (object == self.playerItem) {
            if ([keyPath isEqualToString:@"status"]) {
                AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
                switch (status) {
                    case AVPlayerStatusUnknown:
                    {
                        NSLog(@"未知错误:%@", self.playerItem.error);
                    }
                        break;
                    case AVPlayerStatusReadyToPlay:
                    {
                        NSLog(@"准备播放");
                    }
                        break;
                    case AVPlayerStatusFailed:
                    {
                        NSLog(@"播放失败:%@", self.playerItem.error);
                    }
                        break;
                        
                    default:
                        break;
                }
            }
        }
    }
    //观看结束
    - (void)playerItemDidReachEnd:(NSNotification *)notification {
        self.isWatchFinish = YES;
        [self backClick];
    }
    //进入后台
    - (void)appDidEnterBackground {
        [self playerSetPlay:NO];
    }
    //进入前台
    - (void)appDidEnterPlayground {
        
    }
    //检测耳机状态
    - (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
        NSDictionary *interuptionDict = notification.userInfo;
        NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
        switch (routeChangeReason) {
            case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
                NSLog(@"耳机插入");
                break;
            case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
            {
                NSLog(@"耳机拔出");
                [self playerSetPlay:NO];
            }
                break;
                
            default:
                break;
        }
    }
    
    #pragma mark - Utils
    
    - (NSString *)getStringFromCMTime:(CMTime)time {
        float currentTimeValue = (CGFloat)time.value/time.timescale;//得到当前的播放时
        
        NSDate * currentDate = [NSDate dateWithTimeIntervalSince1970:currentTimeValue];
        NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
        NSInteger unitFlags = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond ;
        NSDateComponents *components = [calendar components:unitFlags fromDate:currentDate];
        
        if (currentTimeValue >= 3600 ) {
            return [NSString stringWithFormat:@"%@:%@:%@",components.hour < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.hour] : [NSString stringWithFormat:@"%ld", (long)components.hour], components.minute < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.minute] : [NSString stringWithFormat:@"%ld", (long)components.minute], components.second < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.second] : [NSString stringWithFormat:@"%ld", (long)components.second]];
        } else {
            return [NSString stringWithFormat:@"%@:%@",components.minute < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.minute] : [NSString stringWithFormat:@"%ld", (long)components.minute], components.second < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.second] : [NSString stringWithFormat:@"%ld", (long)components.second]];
        }
    }
    
    - (NSString *)getVideoLengthFromTimeLength:(CGFloat)timeLength {
        NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeLength];
        NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
        NSInteger unitFlags = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond ;
        NSDateComponents *components = [calendar components:unitFlags fromDate:date];
        
        if (timeLength >= 3600 ) {
            return [NSString stringWithFormat:@"%@:%@:%@",components.hour < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.hour] : [NSString stringWithFormat:@"%ld", (long)components.hour], components.minute < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.minute] : [NSString stringWithFormat:@"%ld", (long)components.minute], components.second < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.second] : [NSString stringWithFormat:@"%ld", (long)components.second]];
        } else {
            return [NSString stringWithFormat:@"%@:%@",components.minute < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.minute] : [NSString stringWithFormat:@"%ld", (long)components.minute], components.second < 10 ? [NSString stringWithFormat:@"0%ld", (long)components.second] : [NSString stringWithFormat:@"%ld", (long)components.second]];
        }
    }
    
    #pragma mark - dealloc
    //回收
    - (void)dealloc {
        [self removeVideoTimerObserver];
        [self removeNotifications];
        [self.avTimer invalidate];
        self.avTimer = nil;
        self.player = nil;
        self.playerItem = nil;
    }
    
    @end
    
    

    相关文章

      网友评论

        本文标题:iOS AVPlayer自定义播放器

        本文链接:https://www.haomeiwen.com/subject/ndiztxtx.html