短视频程序源码,如何实现短视频的热门页面

之前做过一些短视频程序源码和直播项目,但是很多部分使用的是别的公司做好的SDK,由于不想像傻瓜一样不知道具体实现方式的调用来调用去,我决定自己做一个完全开源的,没有任何封装的SDK的短视频程序源码。

在实现短视频程序源码的过程中,我根据市面流行抖音,实现了短视频实现。

推荐页面
抖音样式短视频程序源码推荐页面整体实现的Gif效果:

首先我先说下底层UI的搭建,我们可以看到推荐,热门,附近这三个可以切换的导航按钮是与下面呈现短视频程序源码的UISCrollView联动的(其中推荐和热门我使用的是抖音界面,附近使用的是快手界面),这里我使用了完全开源的TYPagerController三方库,这个三方库的使用很广泛,在滑动切换页面卡顿这方面处理的很好,有兴趣的可以详细去看源码,我在这里简单介绍下实现原理。

我创建的类CustomPagerController继承于三方中的TYTabButtonPagerController,这个滑动导航控制器由两部分组成:

上方的TabBar(CollectionCell、UnderLineView构成)
下方的ScrollView(ViewController.View构成)
下面说一下滑动导航控制器的总体流程:

将ViewController.view加入到下方的ScrollView中
根据数据源Titles对上方TabBar中CollectionCell上的Label赋值
处理下方ScorllView与上方TabBar之间的协同问题
具体代码实现如下:

CustomPagerController.h

#import "TYTabButtonPagerController.h"
@interface CustomPagerController : TYTabButtonPagerController
 
@end

CustomPagerController.m 

#import "CustomPagerController.h"
 
#import "RecommendVideoVC.h"
#import "HotVideoVC.h"
#import "NearByVideoVC.h"
 
//Alan change
@interface CustomPagerController(){
    
}
@property(nonatomic,strong)UIButton *search;
@property(nonatomic,strong)NSArray *infoArrays;
@end
@implementation CustomPagerController
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    self.navigationController.navigationBar.hidden = YES;
 
}
-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    
}
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
}
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
    self.navigationController.navigationBarHidden = YES;
    [self.view addSubview:self.search];
    self.adjustStatusBarHeight = YES;
    self.cellSpacing = 8;
    self.infoArrays = [NSArray arrayWithObjects:@"推荐",@"热门",@"附近",nil];
    [self setBarStyle:TYPagerBarStyleProgressView];
    [self setContentFrame];
    
}
 
#pragma mark - TYPagerControllerDataSource
- (NSInteger)numberOfControllersInPagerController {
    return self.infoArrays.count;
}
- (NSString *)pagerController:(TYPagerController *)pagerController titleForIndex:(NSInteger)index {
    return self.infoArrays[index];
}
- (UIViewController *)pagerController:(TYPagerController *)pagerController controllerForIndex:(NSInteger)index {
 
    if(index == 0){
        //推荐
        RecommendVideoVC *videoVC = [[RecommendVideoVC alloc]init];
        NSString *url = [purl stringByAppendingFormat:@"?service=Video.getRecommendVideos&uid=%@&type=0",[Config getOwnID]];
        videoVC.requestUrl = url;
 
        return videoVC;
    } else if(index == 1) {
        //热门
        HotVideoVC *videoVC= [[HotVideoVC alloc]init];
        videoVC.ismyvideo = 0;
        NSString *url = [purl stringByAppendingFormat:@"?service=Video.getVideoList&uid=%@&type=0",[Config getOwnID]];
        videoVC.url = url;
 
        return videoVC;
    }else if(index == 2) {
        //附近
        NearByVideoVC *videoVC= [[NearByVideoVC alloc]init];
 
        return videoVC;
    }else{
       
        return nil;
    }
}
#pragma mark - override delegate
- (void)pagerController:(TYTabPagerController *)pagerController configreCell:(TYTabTitleViewCell *)cell forItemTitle:(NSString *)title atIndexPath:(NSIndexPath *)indexPath {
   [super pagerController:pagerController configreCell:cell forItemTitle:title atIndexPath:indexPath];
}
- (void)pagerController:(TYTabPagerController *)pagerController didSelectAtIndexPath:(NSIndexPath *)indexPath{
   
    NSLog(@"wmplayer:===6.28===%ld",(long)indexPath.row);
}
- (void)pagerController:(TYTabPagerController *)pagerController didScrollToTabPageIndex:(NSInteger)index{
   
    
}
#pragma mark - set/get
-(UIButton *)search {
 
    if (!_search) {
        _search = [UIButton buttonWithType:0];
        [_search setImage:[UIImage imageNamed:@"home_search"] forState:0];
        _search.frame = CGRectMake(_window_width-50, 20+statusbarHeight, 40, 40);
        [_search addTarget:self action:@selector(doSearchBtn) forControlEvents:UIControlEventTouchUpInside];
    }
    return _search;
}
 
@end

接下来就是重点了,对于抖音界面呈现样式的详细介绍。

整体UI静态图:

我先简单说下UI层级的搭建,它最下面是一个UIScrollView,这是实现上下滑动播放的基础,在这里我们要介绍一个很重要的属性,这是UIScrollView滚动自动换页的关键,就是pagingEnabled属性,一定要设置为YES。UIScrollView的上层是三个UIImageView,这三个UIIMageView是实现无限轮播的关键,播放器就铺在UIImageView上面。最上面是包括点赞,评论,滚动唱片和歌曲名字等的一个UIView,这个UIView会在每一个UIIMageView上放一个,它们都是会预先加载的,这样在滑动的时候就不会有卡顿和画面不流畅的问题。这里多说一句,UIIMageView只是会预先加载视频的第一帧,也就是一张图片,并不会预先加载要播放的视频,只有滑动到每个UIImageView的时候,才会开始加载当前的UIIMageView上的视频。

这是UI主要的代码:
 

-(UIScrollView *)backScrollView{
    if (!_backScrollView) {
        _backScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, _window_width, _window_height)];
        _backScrollView.contentSize =  CGSizeMake(_window_width, _window_height*3);
        _backScrollView.userInteractionEnabled = YES;
        _backScrollView.pagingEnabled = YES;//设为YES当滚动的时候会自动跳页
        _backScrollView.showsVerticalScrollIndicator = NO;
        _backScrollView.showsHorizontalScrollIndicator =NO;
        _backScrollView.delegate = self;
        _backScrollView.scrollsToTop = NO;
        _backScrollView.bounces = NO;
        _backScrollView.backgroundColor = [UIColor clearColor];
        
        _firstImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, _window_width, _window_height)];
        _firstImageView.image = [UIImage imageNamed:@""];
        _firstImageView.contentMode = UIViewContentModeScaleAspectFill;
        _firstImageView.clipsToBounds = YES;
        [_backScrollView addSubview:_firstImageView];
        _firstImageView.jp_videoPlayerDelegate = self;
        
        _secondImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, _window_height, _window_width, _window_height)];
        _secondImageView.image = [UIImage imageNamed:@""];
        _secondImageView.contentMode = UIViewContentModeScaleAspectFill;
        _secondImageView.clipsToBounds = YES;
        [_backScrollView addSubview:_secondImageView];
        _secondImageView.jp_videoPlayerDelegate = self;
        
        _thirdImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, _window_height*2, _window_width, _window_height)];
        _thirdImageView.image = [UIImage imageNamed:@""];
        _thirdImageView.contentMode = UIViewContentModeScaleAspectFill;
        _thirdImageView.clipsToBounds = YES;
        [_backScrollView addSubview:_thirdImageView];
        _thirdImageView.jp_videoPlayerDelegate = self;
        
        WeakSelf;
        _firstFront = [[FrontView alloc]initWithFrame:_firstImageView.frame callBackEvent:^(NSString *type) {
            [weakSelf clickEvent:type];
        }];
        [_backScrollView addSubview:_firstFront];
        
        _secondFront = [[FrontView alloc]initWithFrame:_secondImageView.frame callBackEvent:^(NSString *type) {
            [weakSelf clickEvent:type];
        }];
        [_backScrollView addSubview:_secondFront];
        
        _thirdFront = [[FrontView alloc]initWithFrame:_thirdImageView.frame callBackEvent:^(NSString *type) {
            [weakSelf clickEvent:type];
        }];
        [_backScrollView addSubview:_thirdFront];
    }
    
    return _backScrollView;
}

基本的UI实现比如点赞按钮一类的我就不介绍了,我主要介绍下左下角滚动的音乐名字和右下角转动的唱片音符这两个动画实现方式。

这两类动画主要是通过核心动画框架QuartzCore来实现的,话不多说看代码:

跑马灯式的左下角音乐名字滚动,实现原理是使用UIView动画使两个紧挨的Label同时向左匀速变动位置。

    _label.text = _contentStr;
    _label2.text = _label.text;
    
    CGSize sizee = [PublicObj sizeWithString:_label.text andFont:SYS_Font(15)];//自适应大小
    CGFloat withdd = MAX(self.frame.size.width,sizee.width)+20;
    _label.frame = CGRectMake(0.0, 0.0, withdd, self.frame.size.height);
    _label2.frame = CGRectMake(withdd, 0.0, withdd, self.frame.size.height);
    // 动画
    [UIView beginAnimations:@"testAnimation" context:NULL];
    [UIView setAnimationDuration:3.0f];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    [UIView setAnimationRepeatCount:999999];
    
    CGRect frame = _label.frame;
    frame.origin.x = -withdd;
    _label.frame = frame;
    
    CGRect frame2 = _label2.frame;
    frame2.origin.x = 0.0;
    _label2.frame = frame2;
    [UIView commitAnimations];

音符动画的实现原理是通过CAAnimationGroup动画组同时实现移动,放大及渐变透明三个动画,代码中对核心部分进行了详细注释。

#pragma mark - 音符动画组
+(CAAnimationGroup*)caGroup{
    //动画组,用来保存一组动画对象
    CAAnimationGroup *group = [[CAAnimationGroup alloc]init];
    //路径
    CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    pathAnimation.calculationMode = kCAAnimationPaced;//kCAAnimationPaced 使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效;
    pathAnimation.fillMode = kCAFillModeForwards;//当动画结束后,layer会一直保持着动画最后的状态
    pathAnimation.removedOnCompletion = YES;//默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态
    CGMutablePathRef curvedPath = CGPathCreateMutable();//创建一个可变路径
    //起点
    CGPathMoveToPoint(curvedPath, NULL, 45, 350);//(45,350)起点
    //辅助点和终点--- 父视图 85*350(唱片 50*50 )
    CGPathAddQuadCurveToPoint(curvedPath, NULL, 8, 340, 16, 290);//(16,290)终点
    pathAnimation.path = curvedPath;
    CGPathRelease(curvedPath);
    
    //缩放
    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
    NSMutableArray *values = [NSMutableArray array];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 1.2, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.3, 1.3, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.4, 1.4, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.6, 1.6, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.7, 1.7, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.8, 1.8, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.9, 1.9, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0, 2.0, 1.0)]];
    [values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(2.1, 2.1, 1.0)]];
    
    animation.values = values;
    
    //透明
    CAKeyframeAnimation *opacityAnimaton = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
    opacityAnimaton.values = @[@1,@1,@1,@1,@1,@0.9,@0.8,@0.7,@0.6,@0.5,@0.4,@0.3];
    
    group.animations = @[pathAnimation,animation,opacityAnimaton];
    group.duration = 3.0;
    group.repeatCount = MAXFLOAT;
    group.fillMode = kCAFillModeForwards;//定义定时对象在其活动持续时间之外的行为。
    return group;
}

 唱片旋转动画的原理是使用方法animationWithKeyPath:对 CABasicAnimation进行实例化,并指定Layer的旋转属性作为关键路径进行注册,使其不断旋转。

#pragma mark - 唱片旋转动画
+(CABasicAnimation*)rotationAnimation {
    CABasicAnimation *rotate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    rotate.toValue = @(2 * M_PI);//结束时转动的角度
    rotate.duration = 5;//动画时长
    rotate.repeatCount = MAXFLOAT;//无限重复
    return rotate;
}

下面要说的就是重中之重的滑动播放视频了。

当我们首次进入抖音播放短视频页面时,会优先判断当前的视频列表videoList是否有值,如果没有值或当前的视频的index大于videoList.count - 3 时,就会重新请求服务端,获取新的一组短视频。

下面时核心代码

if (!_videoList || _videoList.count == 0) {
        _isHome = YES;
        _currentIndex = 0;
        _pages = 1;
        self.videoList = [NSMutableArray array];
        
        [self requestMoreVideo];//请求数据并加载页面
}
if (_currentIndex>=_videoList.count-3) {
        _pages += 1;
        [self requestMoreVideo];//请求数据并加载页面
    }
- (void)requestMoreVideo { 
    WeakSelf;
    [YBNetworking postWithUrl:url Dic:nil Suc:^(NSDictionary *data, NSString *code,  NSString *msg) {
        if ([code isEqual:@"0"]) {
            NSArray *info = [data valueForKey:@"info"];
            if (_pages==1) {
                [_videoList removeAllObjects];
            }
            [_videoList addObjectsFromArray:info];
            if (_isHome == YES) {
                _isHome = NO;
                _scrollViewOffsetYOnStartDrag = -100;
                [weakSelf scrollViewDidEndScrolling];//加载页面
            }
        }
    } Fail:^(id fail) {
        
    }];
}


结下来我们要介绍加载页面的三种情况,这里我们会用到三个UIImageView,为firstImageView、secondImageView,thirdImageView,对应三个展示UI的View,分别为firstFront、secondFront、thirdFront,对应三个数据源lastHostDic、hostdic、nextHostDic:

第一种是刚进来currentIndex == 0(currentIndex是指当前滚动到第几个视频),这时候我们要设置UIScrollView的ContentOffset为(0,0), currentPlayerIV(当前UIIMageView)为firstImageView,currentFront(当前呈现UI的View)为firstFront。并且要预加载secondImageView的数据,这里不用处理thirdImageView,因为只能向下滑,不需要预加载thirdImageView并且滚到第二个的时候自然给第三个赋值:

//第一个
        [self.backScrollView setContentOffset:CGPointMake(0, 0) animated:NO];
        _currentPlayerIV = _firstImageView;
        _currentFront = _firstFront;
        
        /**
         *  _currentIndex=0时,重新处理下_secondImageView的封面、
         *  不用处理_thirdImageView,因为滚到第二个的时候上面的判断自然给第三个赋值
         */
        [_firstImageView sd_setImageWithURL:[NSURL URLWithString:minstr([_hostdic valueForKey:@"thumb"])]];
        [self setUserData:_hostdic withFront:_firstFront];
        [self setVideoData:_hostdic withFront:_firstFront];
        [_secondImageView sd_setImageWithURL:[NSURL URLWithString:minstr([_nextHostDic valueForKey:@"thumb"])]];
        [self setUserData:_nextHostDic withFront:_secondFront];
        [self setVideoData:_nextHostDic withFront:_secondFront];

这里的setUerData和setVideoData是给页面加载数据的,详细实现为:

-(void)setUserData:(NSDictionary *)dataDic withFront:(FrontView*)front{
 
    NSDictionary *musicDic = [dataDic valueForKey:@"musicinfo"];
    id userinfo = [dataDic valueForKey:@"userinfo"];
    NSString *dataUid;
    NSString *dataIcon;
    NSString *dataUname;
    if ([userinfo isKindOfClass:[NSDictionary class]]) {
        dataUid = [NSString stringWithFormat:@"%@",[userinfo valueForKey:@"id"]];
        dataIcon = [NSString stringWithFormat:@"%@",[userinfo valueForKey:@"avatar"]];//右边最上面的带➕的头像图片
        dataUname = [NSString stringWithFormat:@"@%@",[userinfo valueForKey:@"user_nicename"]];//左下角第一行@的作者名
    }else{
        dataUid = @"0";
        dataIcon = @"";
        dataUname = @"";
    }
    
    NSString *musicID = [NSString stringWithFormat:@"%@",[musicDic valueForKey:@"id"]];
    NSString *musicCover = [NSString stringWithFormat:@"%@",[musicDic valueForKey:@"img_url"]];
    //musicIV右下角转动的唱片上覆盖的歌曲背景图片
    if ([musicID isEqual:@"0"]) {
        [front.musicIV sd_setImageWithURL:[NSURL URLWithString:_hosticon]];
    }else{
        [front.musicIV sd_setImageWithURL:[NSURL URLWithString:musicCover]];
    }
    [front setMusicName:[NSString stringWithFormat:@"%@",[musicDic valueForKey:@"music_format"]]];
    front.titleL.text = [NSString stringWithFormat:@"%@",[dataDic valueForKey:@"title"]];//左下角滚动的文字
    front.nameL.text = dataUname;
    [front.iconBtn sd_setBackgroundImageWithURL:[NSURL URLWithString:dataIcon] forState:UIControlStateNormal placeholderImage:[UIImage imageNamed:@"default_head.png"]];
    
    //广告
    NSString *is_ad_str = [NSString stringWithFormat:@"%@",[dataDic valueForKey:@"is_ad"]];
    NSString *ad_url_str = [NSString stringWithFormat:@"%@",[dataDic valueForKey:@"ad_url"]];
    CGFloat ad_img_w = 0;
    if (![PublicObj checkNull:ad_url_str]&&[is_ad_str isEqual:@"1"]&&![PublicObj checkNull:front.titleL.text]) {
        
        NSString *att_text = [NSString stringWithFormat:@"%@    ",front.titleL.text];
        UIImage *ad_link_img = [UIImage imageNamed:@"广告-详情"];
        NSMutableAttributedString *att_img = [NSMutableAttributedString yy_attachmentStringWithContent:ad_link_img contentMode:UIViewContentModeCenter attachmentSize:CGSizeMake(13, 13) alignToFont:SYS_Font(15) alignment:YYTextVerticalAlignmentCenter];
        NSMutableAttributedString *title_att = [[NSMutableAttributedString alloc]initWithString:att_text];
        //NSLog(@"-==-:%@==:%@==img:%@",att_text,title_att,att_img);
        [title_att appendAttributedString:att_img];
        NSRange click_range = [[title_att string] rangeOfString:[att_img string]];
        title_att.yy_font = SYS_Font(15);
        title_att.yy_color = [UIColor whiteColor];
        title_att.yy_lineBreakMode = NSLineBreakByTruncatingHead;
        title_att.yy_kern = [NSNumber numberWithFloat:0.2];
        [title_att addAttribute:NSBackgroundColorAttributeName value:[UIColor clearColor] range:click_range];
        [title_att yy_setTextHighlightRange:click_range color:[UIColor clearColor] backgroundColor:[UIColor clearColor] tapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {
            //[YBMsgPop showPop:@"1111111"];
            [self adJump:ad_url_str];
        }];
        front.titleL.preferredMaxLayoutWidth =_window_width*0.75;
        front.titleL.attributedText = title_att;
        
        ad_img_w = 30;
    }
    
    //计算名称长度 最长3行高度最大60
    CGSize titleSize = [front.titleL.text boundingRectWithSize:CGSizeMake(_window_width*0.75, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:SYS_Font(15)} context:nil].size;
    CGFloat title_h = titleSize.height>60?60:titleSize.height;
    CGFloat title_w = _window_width*0.75;//titleSize.width>=(_window_width*0.75)?titleSize.width:titleSize.width+ad_img_w;
    front.titleL.frame = CGRectMake(0, front.musicL.top-title_h, title_w, title_h);
    front.nameL.frame = CGRectMake(0, front.titleL.top-25, front.botView.width, 25);
    front.followBtn.frame = CGRectMake(front.iconBtn.left+12, front.iconBtn.bottom-13, 26, 26);
    //广告
    if ([is_ad_str isEqual:@"1"]) {
        front.adLabel.hidden = NO;
        front.adLabel.frame = CGRectMake(0, front.nameL.top-25, 45, 20);
    }else{
        front.adLabel.hidden = YES;
    }
    
}
-(void)setVideoData:(NSDictionary *)videoDic withFront:(FrontView*)front{
    _shares =[NSString stringWithFormat:@"%@",[videoDic valueForKey:@"shares"]];
    _likes = [NSString stringWithFormat:@"%@",[videoDic valueForKey:@"likes"]];
    _islike = [NSString stringWithFormat:@"%@",[videoDic valueForKey:@"islike"]];
    _comments = [NSString stringWithFormat:@"%@",[videoDic valueForKey:@"comments"]];
    NSString *isattent = [NSString stringWithFormat:@"%@",[NSString stringWithFormat:@"%@",[videoDic valueForKey:@"isattent"]]];
    //_steps = [NSString stringWithFormat:@"%@",[info valueForKey:@"steps"]];
    WeakSelf;
    //dispatch_async(dispatch_get_main_queue(), ^{
    //点赞数 评论数 分享数
    if ([weakSelf.islike isEqual:@"1"]) {
        [front.likebtn setImage:[UIImage imageNamed:@"home_zan_sel"] forState:0];
        //weakSelf.likebtn.userInteractionEnabled = NO;
    } else{
        [front.likebtn setImage:[UIImage imageNamed:@"home_zan"] forState:0];
        //weakSelf.likebtn.userInteractionEnabled = YES;
    }
    [front.likebtn setTitle:[NSString stringWithFormat:@"%@",_likes] forState:0];
    front.likebtn = [PublicObj setUpImgDownText:front.likebtn];
    [front.enjoyBtn setTitle:[NSString stringWithFormat:@"%@",_shares] forState:0];
    front.enjoyBtn = [PublicObj setUpImgDownText:front.enjoyBtn];
    [front.commentBtn setTitle:[NSString stringWithFormat:@"%@",_comments] forState:0];
    front.commentBtn = [PublicObj setUpImgDownText:front.commentBtn];
    
    if ([[Config getOwnID] isEqual:weakSelf.hostid] || [isattent isEqual:@"1"]) {
        front.followBtn.hidden = YES;
    }else{
        [front.followBtn setImage:[UIImage imageNamed:@"home_follow"] forState:0];
        front.followBtn.hidden = NO;
        [front.followBtn.layer addAnimation:[PublicObj followShowTransition] forKey:nil];
    }
    //});
}

第二种是当你用手滑动的时候,currentIndex > 0 并且小于videoList.count - 1(即既不是第一个也不是最后一个视频),这时候会优先触发代理方法:

#pragma mark - scrollView delegate
//开始拖拽
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    lastContenOffset = scrollView.contentOffset.y;
    //NSLog(@"111=====%f",scrollView.contentOffset.y);
    _currentPlayerIV.jp_progressView.hidden = YES;//当前播放进度隐藏
    self.scrollViewOffsetYOnStartDrag = scrollView.contentOffset.y;//记录开始拖拽的contentoffset
}
//结束拖拽
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
                  willDecelerate:(BOOL)decelerate {
    endDraggingOffset = scrollView.contentOffset.y;//记录结束拖拽的位置
    //NSLog(@"222=====%f",scrollView.contentOffset.y);
    if (decelerate == NO) {
        [self scrollViewDidEndScrolling];
    }
}
 
//开始减速
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
    scrollView.scrollEnabled = NO;
    //NSLog(@"333=====%f",scrollView.contentOffset.y);
    
}
 
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    
    //NSLog(@"currentIndex=====%.2f",scrollView.contentSize.height);
}
 
//结束减速
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    scrollView.scrollEnabled = YES;
    //NSLog(@"444=====%f",scrollView.contentOffset.y);
    
    if (lastContenOffset < scrollView.contentOffset.y && (scrollView.contentOffset.y-lastContenOffset)>=_window_height) {
        NSLog(@"=====向上滚动=====");
        
        _currentIndex++;
        if (_currentIndex>_videoList.count-1) {
            _currentIndex =_videoList.count-1;
        }
    }else if(lastContenOffset > scrollView.contentOffset.y && (lastContenOffset-scrollView.contentOffset.y)>=_window_height){
        
        NSLog(@"=====向下滚动=====");
        _currentIndex--;
        if (_currentIndex<0) {
            _currentIndex=0;
        }
    }else{
        NSLog(@"=======本页拖动未改变数据=======");
        if (scrollView.contentOffset.y == 0 && _currentIndex==0) {
            [YBMsgPop showPop:@"已经到顶了哦^_^"];
        }else if (scrollView.contentOffset.y == _window_height*2 && _currentIndex==_videoList.count-1){
            [YBMsgPop showPop:@"没有更多了哦^_^"];
        }
    }
    
    _currentPlayerIV.jp_progressView.hidden = NO;
    [self scrollViewDidEndScrolling];
    
    if (_requestUrl) {
        if (_currentIndex>=_videoList.count-3) {
            _pages += 1;
            [self requestMoreVideo];
        }
    }
    
}
 
#pragma mark - Private
 
- (void)scrollViewDidEndScrolling {
    
    if((self.scrollViewOffsetYOnStartDrag == self.backScrollView.contentOffset.y) && (endDraggingOffset!= _scrollViewOffsetYOnStartDrag)){
        
        return;
    }
    //NSLog(@"7-8==%f====%f",self.scrollViewOffsetYOnStartDrag,self.backScrollView.contentOffset.y);
    [self changeRoom];
    
}

这时当scrollview 滑动自动触发翻页时,则让UIScrollView迅速复位,这时候我们要设置UIScrollView的ContentOffset为(0,_window_height), _window_height为当前屏幕大小,currentPlayerIV(当前UIIMageView)为secondImageView,currentFront(当前呈现UI的View)为secondFront。并且要预加载firstImageView,firstFront和thirdImageView,thirdFront数据的。代码如下:

[_secondImageView sd_setImageWithURL:[NSURL URLWithString:minstr([_hostdic valueForKey:@"thumb"])]];//这里设置视频的第一帧,用于在整个页面显示
    [self setUserData:_hostdic withFront:_secondFront];
    [self setVideoData:_hostdic withFront:_secondFront];
[self.backScrollView setContentOffset:CGPointMake(0, _window_height) animated:NO];
        _currentPlayerIV = _secondImageView;
        _currentFront = _secondFront;
   if (_curentIndex>0) {
        _lastHostDic = _videoList[_curentIndex-1];
        [_firstImageView sd_setImageWithURL:[NSURL URLWithString:minstr([_lastHostDic valueForKey:@"thumb"])]];
        [self setUserData:_lastHostDic withFront:_firstFront];
        [self setVideoData:_lastHostDic withFront:_firstFront];
    }
    if (_curentIndex < _videoList.count-1) {
        _nextHostDic = _videoList[_curentIndex+1];
        [_thirdImageView sd_setImageWithURL:[NSURL URLWithString:minstr([_nextHostDic valueForKey:@"thumb"])]];
        [self setUserData:_nextHostDic withFront:_thirdFront];
        [self setVideoData:_nextHostDic withFront:_thirdFront];
        
    }

 第三种情况是滚动到最后一个,这时候我们要设置UIScrollView的ContentOffset为(0,_window_height*2), _window_height为当前屏幕大小,currentPlayerIV(当前UIIMageView)为thirdImageView,currentFront(当前呈现UI的View)为thirdFront。并且要预加载secondImageView,secondFront数据的。代码如下:

//最后一个
        [self.backScrollView setContentOffset:CGPointMake(0, _window_height*2) animated:NO];
        _currentPlayerIV = _thirdImageView;
        _currentFront = _thirdFront;
        /**
         *  _currentIndex=_videoList.count-1时,重新处理下_secondImageView的封面、
         *  这个时候只能上滑 _secondImageView 给 _lastHostDic的值
         */
        [_secondImageView sd_setImageWithURL:[NSURL URLWithString:minstr([_lastHostDic valueForKey:@"thumb"])]];
        [self setUserData:_lastHostDic withFront:_secondFront];
        [self setVideoData:_lastHostDic withFront:_secondFront];
        [_thirdImageView sd_setImageWithURL:[NSURL URLWithString:minstr([_hostdic valueForKey:@"thumb"])]];
        [self setUserData:_hostdic withFront:_thirdFront];
        [self setVideoData:_hostdic withFront:_thirdFront];

当三种情况都介绍完后就涉及到最终的播放了,这里我使用的是JPVideoPlayer播放器(完全开源的,有兴趣的可以自行下载研究原理),播放的主要代码如下:

    //切记一定要先把当前播放的上一个关闭
    [_currentPlayerIV jp_stopPlay];
    
    //开始播放
    [_currentPlayerIV jp_playVideoMuteWithURL:[NSURL URLWithString:_playUrl]
                           bufferingIndicator:[JPBufferView new]
                                 progressView:[JPLookProgressView new]
                                configuration:^(UIView *view, JPVideoPlayerModel *playerModel) {
                                    view.jp_muted = NO;//播放器的音频输出是否静音
                                    _firstWatch = YES;
                                    if (_currentPlayerIV.image.size.width>0 && (_currentPlayerIV.image.size.width >= _currentPlayerIV.image.size.height)) {
                                        playerModel.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
                                    }else{
                                        playerModel.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
                                    }
                                }];

下面有几个需要设置的重要代理

1)实现重复播放

//return返回NO以防止重播视频。 返回YES则重复播放视频。如果未实施,则默认为YES
- (BOOL)shouldAutoReplayForURL:(nonnull NSURL *)videoURL
{
   return YES;
}

2)播放状态改变时需要做的相应处理,主要是页面消失的时候停止播放 

//播放状态改变的时候触发
-(void)playerStatusDidChanged:(JPVideoPlayerStatus)playerStatus {
    NSLog(@"=====7-8====%lu",(unsigned long)playerStatus);
    
    if (_stopPlay == YES) {
        NSLog(@"8-4:play-停止了");
        _stopPlay = NO;
        _firstWatch = NO;
        //页面已经消失了,就不要播放了
        [_currentPlayerIV jp_stopPlay];
    }
    
    if (playerStatus == JPVideoPlayerStatusPlaying) {
        if (_bufferIV) {
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [_bufferIV removeFromSuperview];
            });
        }
    }
    if (playerStatus == JPVideoPlayerStatusReadyToPlay && _firstWatch==YES) {
        //addview
        
    }
    if (playerStatus == JPVideoPlayerStatusStop && _firstWatch == YES) {
        //finish
        _firstWatch = NO;
        
    }
    
}

声明:本文由云豹科技转发自a z q博客,如有侵权请联系作者删除
原文链接:https://blog.csdn.net/weixin_42433480/article/details/90295434

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可。源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可。源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可。源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可。源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可。源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可。源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可。源码是经过本地编译可运行的,下载完成之后配置相应环境即可使用。源码功能都是经过老师肯定的,都能满足要求,有需要放心下载即可。源码是经
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值