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

之前做过一些短视频程序源码和直播项目,但是很多部分使用的是别的公司做好的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行高度最大60CGSize 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 *)scrollViewwillDecelerate:(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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/71965.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

短视频源码APP开发——短视频的功能

说到短视频&#xff0c;首先想到的就是抖音快手。毫无疑问&#xff0c;这两款短视频app是目前最受欢迎的&#xff0c;各大巨头都在布局短视频市场。短视频相比传统的文字、图片&#xff0c;在宣传和传播方面&#xff0c;短视频更有趣&#xff0c;传播范围广&#xff0c;短短几分…

什么是爆款视频生成器?怎么开发搭建

一、 什么是爆款视频生成器&#xff1f; 爆款视频生成器&#xff0c;是为创作者提供全方位创作灵感&#xff0c;创意脚本支持的短视频生成工具&#xff0c; 爆款文案热门话题创意标题&#xff0c;模板化视频制作加持&#xff0c;助力商家流量提升及团单转化的一种微信小程序。…

图文一键生成短视频工具

文字转视频好用的工具有哪些&#xff1f;文字图片一键自动朗读转视频文件解放双手&#xff0c;一刀工具箱提供在线文字转语音短视频制作必备工具。 代码片段 //2018.9.22 下载与打包生成视频static function buildMp4($id){if($id){$lists self::where("is_convert&qu…

短视频源码,成品短视频app源码搭建第一步

随着短视频平台的兴起&#xff0c;短视频app也成为了移动应用市场的一大风口。开发一款成品的短视频app需要大量的资源和时间&#xff0c;而使用现成的小视频app源码则可以快速地搭建出一个基础功能完备的短视频app。本文将介绍如何选择适合自己的小视频app源码&#xff0c;作为…

Google发布文本内容生成短视频工具:Imagen Video

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; AI视频机器人方面的两个突破性进展&#xff0c;一个产品叫imagen video,可以生成1280768的24帧的高清视频片段&#xff0c;另一个叫PHENAKI,可以根据文字描述生成长视频。 最近Google发布文本内容…

时隔 5 年,3 年匠心打造的 Godot Engine 4.0 都带来了哪些惊喜?

【CSDN 编者按】语言经历了漫长的等待&#xff0c;万众瞩目的 Godot Engine 4.0 正式版在其 3.0 版本发布 5 年以后&#xff0c;终于带着海量令人兴奋的新功能横空出世&#xff01; 整理 | 开发游戏的老王 责编 | 王子彧 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews…

chatgpt赋能python:Python中如何暂停程序的执行

Python中如何暂停程序的执行 Python是一种高级的编程语言&#xff0c;它的语法简洁、易于理解&#xff0c;并且在各种领域都有广泛的应用。在编写代码时&#xff0c;我们经常需要在程序的执行过程中暂停或延迟某些操作&#xff0c;这在很多情况下对程序的性能、稳定性和可读性…

【GPT】文本生成任务(生成摘要、文本纠错、机器翻译等的模型微调)

note 文章目录 note一、NLG任务二、NLG之文本摘要2.1 基于mT5的文本摘要2.2 基于openai接口测试2.3 基于chatGPT接口 三、根据自己的数据集进行模型微调四、文本纠错任务五、机器翻译任务Reference 一、NLG任务 NLG&#xff1a;自然语言生成任务&#xff0c;很多NLP任务可以被…

英语四六级考试技巧/英语四六级真题

先问一个残忍的问题&#xff1a; 同学&#xff0c;四六级英语考试准备好了吗 介绍一下四六级占分比例&#xff1a;写作 15%听力 35%阅读 35%翻译 15% 如果时间够多的话 当然是积累词汇量了选择一款背单词APP准备起来没事听听英语听力&#xff0c;找到感觉 如果时间不够多的话&…

服务器远程拒绝访问解决办法之一

把如图所示的用户权限分配中的【拒绝通过远程桌面服务登录】里面的用户删掉。

拒绝用户访问

Failed to execute goal org.mybatis.generator:mybatis-generator-maven-plugin:1.3.6:generate (default-cli) on project booksys: Access denied for user ‘root’‘localhost’ (using password: 出现这个问题有很多种可能导致&#xff1a; 解决方案1&#xff1a; 重新启…

访问 www.xxx.com 的请求遭到拒绝您未获授权,无法查看此网页。HTTP ERROR 403

该项目是一个在本地搭建的微服务项目&#xff0c;结果所有的模块都没报错&#xff0c;转换工具也都搞定&#xff0c;用谷歌访问本地出现403错误&#xff0c;而在微软上访问正常&#xff0c;经过查证才知道是自己的访问路径上加了谷歌自带的加密协议&#xff0c;原来是https://x…

不止于专业:解锁10种提升职业竞争力的秘密武器

职场中的软技能是在工作和职业发展中非常重要的能力&#xff0c;它们与专业技能&#xff08;硬技能&#xff09;一起&#xff0c;共同构成了一个成功职业生涯所需的综合素质。 这类技能为软技能&#xff0c;我也称之为可迁移技能&#xff0c;养成后&#xff0c;你切换在任何一个…

史上最全从0开始教你玩转wsl2+docker,构建自己的开发环境

1、安装wsl 需要windows版本大于 搜索启用或关闭windows功能 把图片中红点标注的功能勾选&#xff0c;注意勾选hyper-v就不能使用虚拟机类软件&#xff0c;如vm&#xff0c;安卓模拟器一类&#xff0c;点击确定&#xff0c;重启电脑。 打开任务管理器 确保虚拟化已经启用&…

Android 包体积优化建议-资源篇

这是 Android 官方文档给出的一些减少包体积的建议&#xff0c;最近正好在看这方面内容&#xff0c;顺手记录、分享一下。本文主要是针对 Android 的资源(Resource) 相关优化建议。 1 移除无用资源 使用 lint 静态代码分析工具查找出未使用的 res 资源。 注意 assert 和 lib…

云炬Android开发笔记 2-2 Android studio项目上传到Github及无法连接Github的问题处理

本文将介绍如何将AS上的项目发布到GitHub上&#xff1a; 选择 VCS——enable verSion control integrate &#xff1a; 选择GIT&#xff1a; 此时可以发现左侧的类都变红了&#xff1a; 此处的颜色含义&#xff1a; 绿色&#xff0c;已经加入控制暂未提交 红色&#xff0c;未加…

【Python】JupyterLab 4.0 来了

来源丨Deephub Imba JupyterLab 是 Jupyter Notebook 的下一代版本&#xff0c;它提供了更强大的功能和更灵活的用户界面&#xff0c;6月6日&#xff0c;官方发布了JupyterLab 4.0的说明&#xff0c;并且说该版本是下一个主要的版本。 JupyterLab的主要改进是: 用户界面&#x…

关于安装基于Anaconda的Pytorch报错问题(文章为所有安装步骤)

由于一开始Pycharm无法下载资源包&#xff0c;所以打算重新安装一下Anaconda&#xff0c;没想到想在官网下载Anaconda时文件丢失&#xff0c;缺少Sprits文件&#xff0c;在网上尝试了很多方法也没有成功&#xff0c;最后猜测应该是之前卸载Anaconda时没有卸载干净&#xff0c;导…

分享一个免费开源的视频剪辑软件(Shotcut)-附带安装教程以及中文设置

Shotcut是什么? Shotcut是一个免费开源跨平台的视频编辑器,内置丰富的视频效果,音频效果,转场效果,最重要是shotcut这款软件操作极其简单,尤其适合新手,是新手入门自媒体的最佳工具 下载Shotcut 官网地址: https://www.shotcut.org 官网下载页面: https://www.shotcut.org/…

剪映专业版大更新:一款全能好用的视频编辑工具

剪映专业版 for Mac是一款全能好用的视频编辑工具&#xff0c;使用剪映专业版能够轻松对视频进行各种编辑&#xff0c;包括卡点、去水印&#xff0c;特效制作、倒放、变速等&#xff0c;还有专业风格滤镜&#xff0c;精选贴纸给你的视频加点乐趣。 本次更新功能&#xff1a; …