短影片程式原始碼,如何實現短影片的熱門頁面
之前做過一些短影片程式原始碼和直播專案,但是很多部分使用的是別的公司做好的SDK,由於不想像傻瓜一樣不知道具體實現方式的呼叫來呼叫去,我決定自己做一個完全開源的,沒有任何封裝的SDK的短影片程式原始碼。
在實現短影片程式原始碼的過程中,我根據市面流行抖音,實現了短影片實現。
推薦頁面
首先我先說下底層
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層級的搭建,它最下面是一個 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
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69982461/viewspace-2779972/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 短影片程式,仿抖音短影片熱門頁面開發
- 短影片app原始碼,Flutter元件--搜尋頁面佈局APP原始碼Flutter元件
- 短影片程式原始碼,怎麼進行短影片稽核機制的架構原始碼架構
- 短影片整套原始碼,如何實現冪等性校驗?原始碼
- 騰訊上面影視類短影片用哪個軟體採集,短影片搬運如何上熱門?
- 如何實現 iOS 短影片跨頁面的無痕續播?iOS
- 短視訊程式原始碼,PageSlider實現滑動頁面原始碼IDE
- 短影片app開發的付費熱門是什麼,短影片依舊內容為王APP
- 短影片的“火”,離不開短影片app原始碼的這三個核心功能APP原始碼
- 短影片自媒體怎麼入門?入門短影片的秘籍看這
- 移動短影片直播開發,短影片原始碼搭建社交平臺原生APP原始碼APP
- 短影片
- 如何實現 Android 短影片跨頁面的流暢續播?Android
- 商家如何快速建立短影片群聊,實現變現?
- 短影片app原始碼,實現冪等設計的常見方式APP原始碼
- 短影片文案提取的簡單實現
- Android短影片系統硬編碼—實現音影片編碼(三)Android
- Android短影片系統硬編碼—實現音影片編碼(二)Android
- 短影片社交平臺開發,短影片直播帶貨,成品原始碼二次開發原始碼
- 短影片app程式碼,如何實現聯表資料查詢和刪除?APP
- 實現Inshot功能的短影片應用
- 短影片電商系統,編寫延遲訊息實現程式碼
- php開源短影片原始碼,如何快速修改MySQL的表結構?PHP原始碼MySql
- 秘樂短影片原始碼系統開發搭建原始碼
- 短影片app原始碼,自定義快速捲軸FastScrollBarAPP原始碼AST
- 實現快剪輯功能的短影片應用
- 短影片軟體開發是如何實現美顏功能的
- 短影片批次釋出軟體,一鍵釋出短影片
- 短影片平臺開發,首先要搭建短影片框架框架
- 六點概括短影片系統原始碼的實用功能機制原始碼
- 短影片原始碼,實現預處理防止客戶端頻繁請求原始碼客戶端
- 所有短影片平臺有哪些?有什麼上熱門的技巧?
- 短影片營銷工具助力商家原創內容創作,直衝熱門影片榜首!
- 短影片原始碼的開發搭建,哪些功能是重點?原始碼
- 直播短影片原始碼,延遲任務的解決方法原始碼
- 短影片批次管理軟體,批次管理多個短影片賬號
- 短影片APP系統原始碼,定製開發功能APP原始碼
- 短影片app原始碼,Vue3滾動載入APP原始碼Vue