iOS实现视频效果和部分动画效果音频&视频IOS相关

ios-自定义AVPlayer-可以点击拖动的进度条

2017-03-15  本文已影响3155人  噢啦噢啦噢啦噢啦噢啦

上图:



上代码:

#import "FQQVideoPlayerViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "BottomView.h"

typedef NS_ENUM(NSInteger, PlayerStatu){
    None,
    End,
    Play,
    Pause
};

@interface FQQVideoPlayerViewController () <BottomViewDelegate>

@property (nonatomic) CGFloat duration;
@property (nonatomic) CGFloat fps;
@property (nonatomic) AVPlayer *player;
@property (nonatomic) id playerObserve;
@property (nonatomic) BottomView *bottomView;
@property (nonatomic) PlayerStatu playerStatu;
@property (nonatomic) BOOL isSliding;
@property (nonatomic) UITapGestureRecognizer *tap;

@end

@implementation FQQVideoPlayerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    //如果不是ios10.1
    if(![self isIOS10_1]){
        [self initAVPlayer];
    }
    
    [self initBottomView];
}

- (void)viewDidAppear:(BOOL)animated{
    if([self isIOS10_1]){
        [self initAVPlayer];
        [self.view bringSubviewToFront:_bottomView];
    }
    [_player play];
    _playerStatu = Play;
}

说明一下,AVplayer在ios10.1有个BUG,在viewDidLoad初始化有很大几率发生AVPlayerItemStatusFailed,然后avplayer就是黑乎乎的。在其他系统一切正常。不过你把初始化放在viewDidApperar,一切也正常。这个解决方法开始是因为一个同事说他在viewDidLoad用dispatch_after等个5秒后再初始化能解决这个问题,然后我的直觉告诉我会不会在viewWillAppear或viewDidAppear就可以了呢。一试,果然,viewDidAppear完美运行。别问我为什么,就是直觉。

- (void)initAVPlayer{
    AVPlayerItem *item = [[AVPlayerItem alloc]initWithURL:_url];
    _duration = CMTimeGetSeconds(item.asset.duration);
    _fps = [[[item.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] nominalFrameRate];
    _player = [[AVPlayer alloc]initWithPlayerItem:item];
    AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:_player];
    layer.frame = self.view.bounds;
    layer.videoGravity = AVLayerVideoGravityResizeAspect;
    [self.view.layer addSublayer:layer];

    //这里监听avplayer是否播放完成,完成调用playDidFinished:
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(playDidFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];
    
    //用弱引用避免互相引用
    __weak typeof(self) weakSelf = self;
    //对于1分钟以内的视频就每1/30秒刷新一次页面,大于1分钟的每秒一次就行
    CMTime interval = _duration > 60 ? CMTimeMake(1, 1) : CMTimeMake(1, 30);
    //这个方法就是每隔多久调用一次block,函数返回的id类型的对象在不使用时用-removeTimeObserver:释放,官方api是这样说的
    _playerObserve = [_player addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        if(!weakSelf.isSliding){
            CGFloat currentTime = CMTimeGetSeconds(time);
            NSString *timeText = [NSString stringWithFormat:@"%@/%@", [weakSelf convert:currentTime], [weakSelf convert:weakSelf.duration]];
            weakSelf.bottomView.timeLabel.text = timeText;
            
            weakSelf.bottomView.slider.value = currentTime / weakSelf.duration;
        }
    }];
}

- (void)playDidFinished:(NSNotification *)notification{
    [_bottomView.playButton setBackgroundImage:[UIImage imageNamed:@"btn_video_play"] forState:UIControlStateNormal];
    [_player pause];
    _playerStatu = End;
}

关于duration,AVPlayerItem里也有一个duration,不过你直接拿_duration = CMTimeGetSeconds(item.duration),打印出来是NAN(南!?),_duration = CMTimeGetSeconds(item.asset.duration)是正确的。好奇怪,难道AVPlayerItem的duration不是从item里的asset里取的吗?
接下来是BottomView

#import <UIKit/UIKit.h>

@protocol BottomViewDelegate

- (void)play;

@end

@interface BottomView : UIView

@property (nonatomic, weak) id<BottomViewDelegate> delegate;
@property (weak, nonatomic) IBOutlet UIButton *playButton;
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;
@property (weak, nonatomic) IBOutlet UISlider *slider;

@end

@implementation BottomView

- (void)awakeFromNib{
    [super awakeFromNib];
    [_slider setThumbImage:[UIImage imageNamed:@"tab_progress_dot"] forState:UIControlStateNormal];
}

- (IBAction)play:(id)sender {
    [_delegate play];
}

@end

没错,进度条就是用UISlider来实现的。但是你一定要拖动那个圆点才能拖动,点击是徒劳的。为什么不把点击效果也弄上呢,苹果的官方控件有时就是这么莫名其妙啊,TimePicker的文字颜色大小不能改,TextField内部明明有placeHolder但又不给使用,页面布局竟然没有TableLayout,LinearLayout?(楼主是Android转过来的)只好自己加上点击事件

- (void)initBottomView{
    _bottomView = [[[NSBundle mainBundle]loadNibNamed:@"BottomView" owner:self options:nil]firstObject];
    _bottomView.frame = CGRectMake(0, 667 - 125, 375, 125);
    _bottomView.delegate = self;
    [self.view addSubview:_bottomView];

    [_bottomView.slider addTarget:self action:@selector(handleTouchDown:) forControlEvents:UIControlEventTouchDown];
    [_bottomView.slider addTarget:self action:@selector(handleSlide:) forControlEvents:UIControlEventValueChanged];
    [_bottomView.slider addTarget:self action:@selector(handleTouchUp:) forControlEvents:UIControlEventTouchUpInside];
    [_bottomView.slider addTarget:self action:@selector(handleTouchUp:) forControlEvents:UIControlEventTouchUpOutside];
    
    _tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleTap:)];
    [_tap setNumberOfTouchesRequired:1];
    [_bottomView.slider addGestureRecognizer:_tap];
}

这样在大多数情况下运行是正常的,但如果你只是稍微的拖动slider,tap事件也会触发,详情可以看这里:http://blog.csdn.net/icetime17/article/details/50720545

- (void)handleTouchDown:(UISlider *)slider{
    NSLog(@"TouchDown");
    _tap.enabled = NO;
    _isSliding = YES;
    if(_playerStatu == Play){
        [_player pause];
    }
}

- (void)handleTouchUp:(UISlider *)slider{
    NSLog(@"TouchUp");
    _tap.enabled = YES;
    _isSliding = NO;
    if(_playerStatu == Play){
        [_player play];
    }
}

- (void)handleSlide:(UISlider *)slider{
    CMTime time = CMTimeMakeWithSeconds(_duration * slider.value, _fps);
    
    NSString *timeText = [NSString stringWithFormat:@"%@/%@", [self convert:_duration * slider.value], [self convert:_duration]];
    _bottomView.timeLabel.text = timeText;
    
    [_player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}

- (void)handleTap:(UITapGestureRecognizer *)recognizer{
    NSLog(@"Tap");
    CGPoint touchPoint = [recognizer locationInView:_bottomView.slider];
    CGFloat value = touchPoint.x / CGRectGetWidth(_bottomView.slider.frame);
    [_bottomView.slider setValue:value animated:YES];
    
    if(_playerStatu == Play){
        [_player pause];
    }
    CMTime time = CMTimeMakeWithSeconds(_duration * value, _fps);
    [_player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
        if(_playerStatu == Play){
            [_player play];
        }
    }];
}

还要实现BottomView的delegate

- (void)play{
    if(_playerStatu == End){
        [_player seekToTime:kCMTimeZero toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
            [_bottomView.playButton setBackgroundImage:[UIImage imageNamed:@"btn_video_stop"] forState:UIControlStateNormal];
            [_player play];
            _playerStatu = Play;
        }];
    }else if(_playerStatu == Play){
        [_bottomView.playButton setBackgroundImage:[UIImage imageNamed:@"btn_video_play"] forState:UIControlStateNormal];
        [_player pause];
        _playerStatu = Pause;
    }else if(_playerStatu == Pause){
        [_bottomView.playButton setBackgroundImage:[UIImage imageNamed:@"btn_video_stop"] forState:UIControlStateNormal];
        [_player play];
        _playerStatu = Play;
    }
}

再献上时间转化函数

- (NSString *)convert:(CGFloat)time{
    int minute = time / 60;
    int second = time - minute * 60;
    NSString *minuteString;
    NSString *secondString;
    if(minute < 10){
        minuteString = [NSString stringWithFormat:@"0%d", minute];
    }else{
        minuteString = [NSString stringWithFormat:@"%d", minute];
    }
    if(second < 10){
        secondString = [NSString stringWithFormat:@"0%d", second];
    }else{
        secondString = [NSString stringWithFormat:@"%d", second];
    }
    return [NSString stringWithFormat:@"%@:%@", minuteString, secondString];
}

最后别忘记了

-(void)dealloc{
    [_player removeTimeObserver:_playerObserve];
}

至于ios10.1的系统判断嘛

- (BOOL)isIOS10_1{
    //TODO
    return NO;
}

项目源码和Demo在Github上有,有兴趣的可以去看看
https://github.com/FQQA/iOS

上一篇下一篇

猜你喜欢

热点阅读