仿抖音在線播放器設(shè)計(jì)

仿抖音在線播放器設(shè)計(jì)

一. 文檔大綱

  • 效果預(yù)覽
  • 功能說明
  • 工程說明
  • 無限滑動(dòng)技術(shù)實(shí)現(xiàn)
  • 邊播邊下載技術(shù)實(shí)現(xiàn)
  • 參考資料

Demo地址:MBVideoPlayer

二. 效果預(yù)覽

工程效果預(yù)覽如下圖所示

preview.gif

三. 功能說明

工程實(shí)現(xiàn)了三方面的功能

  • 基于UIScrollView的無限滑動(dòng)功能

在下拉過程中撤嫩,到底部的時(shí)候,加載新的數(shù)據(jù)赃阀,若數(shù)據(jù)無限,則頁(yè)面可以無限滑動(dòng)下去焰檩。

  • 在線視頻的邊播放邊下載功能

視頻播放過程中多柑,會(huì)自動(dòng)下載到本地沙盒中遥椿。支持?jǐn)帱c(diǎn)續(xù)傳功能。

  • 離線播放功能

如果本地存在播放的視頻數(shù)據(jù)掏父,則優(yōu)先播放本地的數(shù)據(jù)笋轨,所以在離線狀態(tài)下也可以進(jìn)行視頻播放。

四. 工程說明

工程結(jié)構(gòu)如下所示:

project.png

說明如下:


MBVideoPlayer
   └── Other
   │   ├── MBAVAssetResourceLoader (h/m)      #播放器數(shù)據(jù)代理類赊淑,用于攔截播放器請(qǐng)求并返回?cái)?shù)據(jù)
   |   └── MBNetworkManager (h/m)             #網(wǎng)絡(luò)訪問類爵政,用于向服務(wù)器請(qǐng)求播放數(shù)據(jù)
   ├── View 
   |   ├── MBScrollView (h/m)                 #ScrollerView類,用來顯示PlayerView
   |   └── MBPlayerView (h/m)                 #視頻播放視圖
   |   └── MBToastLabelView (h/m)             #用于顯示使用過程的提示
   ├── Model 
   |   ├── MBVideoModel (h/m)                 #videoModel類陶缺,包含了視頻下載鏈接钾挟,視頻描述等字段
   |   └── MBURLTaskModel (h/m)               #視頻下載model,包含了當(dāng)前下載進(jìn)度饱岸,視頻大小等信息   
   ├── Controller
       ├── ViewController (h/m)               #主VC掺出,顯示視頻流
       |── MBSettingViewController (h/m)      #設(shè)置界面VC徽千,用于清除緩存數(shù)據(jù)      
       

五. 無限滑動(dòng)技術(shù)實(shí)現(xiàn)

使用UIScrollView實(shí)現(xiàn)無限滑動(dòng),基于具體的應(yīng)用場(chǎng)景汤锨,有不同的實(shí)現(xiàn)方式双抽。這里列舉兩種應(yīng)用場(chǎng)景的使用方式。

1.顯示的View的數(shù)目固定的情況下(這種方式不在本工程中)

當(dāng)顯示View的數(shù)目固定的時(shí)候闲礼,其實(shí)UIScollView上面只需要添加三個(gè)View就能顯示所有的View牍汹,無需為所有的View都添加一個(gè)獨(dú)立的View。實(shí)現(xiàn)的原理如下:

  • 初始化scrollView的時(shí)候柬泽,設(shè)置3個(gè)View慎菲,添加到scrollView中,并通過設(shè)置contentoffset的屬性锨并,讓中間的view顯示到界面上钧嘶。

  • 每次滑動(dòng)的時(shí)候,在scrollViewDidScroll:方法種琳疏,判斷是否滑動(dòng)的下一個(gè)View,如果滑動(dòng)到下一個(gè)View的話闸拿,則繼續(xù)設(shè)置scrollView的contentOffset空盼,讓scrollView復(fù)位,始終讓中間那個(gè)View顯示新荤。

  • 復(fù)位ScrollView之后揽趾,設(shè)置3個(gè)View的位置,如果是UIImageView的話苛骨,其實(shí)直接修改它們的image的值就可以了篱瞎。

代碼如下所示:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
 CGFloat offset = scrollView.contentOffset.y;
 if (self.lives.count) {
     if (offset >= 2*self.frame.size.height) //向下滑
     {
         // 對(duì)ScollView進(jìn)行復(fù)位處理
         scrollView.contentOffset = CGPointMake(0, self.frame.size.height);
         _currentIndex++;
         self.upperImageView.image = self.middleImageView.image;
         self.middleImageView.image = self.downImageView.image;
         
         
         if (_currentIndex == self.lives.count - 1)//獲取最后一張顯示的是什么內(nèi)容
         {
             _downLive = [self.lives firstObject];
         } else if (_currentIndex == self.lives.count)
         {
             _downLive = self.lives[1];
             _currentIndex = 0;
             
         } else
         {
             _downLive = self.lives[_currentIndex+1];
         }
         [self prepareForImageView:self.downImageView withLive:_downLive];
     }
     else if (offset <= 0) //向上滑
     {
         // slides to the upper player
         scrollView.contentOffset = CGPointMake(0, self.frame.size.height);
         _currentIndex--;
         self.downImageView.image = self.middleImageView.image;
         self.middleImageView.image = self.upperImageView.image;
         
         
         if (_currentIndex == 0)
         {
             _upperLive = [self.lives lastObject];
             
         } else if (_currentIndex == -1)
         {
             _upperLive = self.lives[self.lives.count - 2];
             _currentIndex = self.lives.count-1;
             
         } else
         {
             _upperLive = self.lives[_currentIndex - 1];
         }
         [self prepareForImageView:self.upperImageView withLive:_upperLive];
     }
 }
}

2.顯示的view的數(shù)目不固定,通過上拉到底進(jìn)行數(shù)據(jù)加載的場(chǎng)景

當(dāng)要顯示的View的數(shù)目不固定的時(shí)候痒芝,使用上面那種方式對(duì)于數(shù)據(jù)加載時(shí)機(jī)的判斷就會(huì)相對(duì)比較復(fù)雜俐筋,所以考慮使用一個(gè)比較簡(jiǎn)單的方式,第一種方式可以把UIScrollView想成一個(gè)閉環(huán)严衬,3個(gè)View無限循環(huán)澄者。為了實(shí)現(xiàn)上滑到底加載,我們可以考慮把這個(gè)閉環(huán)打開请琳,還是3個(gè)View粱挡。但是3個(gè)view的整體位置,從滑動(dòng)第二個(gè)view開始到倒數(shù)第二個(gè)view之間俄精,相對(duì)位置不變询筏,整體隨著滑動(dòng)向下滑。整體流程竖慧,如下草圖所示:

unlimitscroll.png

示例代碼如下所示


- (void)reveiveNewData:(NSArray *)data {
   ......
         
   if (data.count > 0) {//如果獲取到新的數(shù)據(jù)嫌套,則自動(dòng)上滑顯示
         self.contentSize = CGSizeMake(self.frame.size.width, self.frame.size.height * self.dataArray.count);
         self.contentOffset = CGPointMake(0, self.frame.size.height * self.currentIndexOfImageView);
     }
   ......
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
 CGFloat offset_y = scrollView.contentOffset.y;
 
 CGPoint translatePoint = [scrollView.panGestureRecognizer translationInView:scrollView];
 if (self.dataArray.count == 0) {
     return;
 }
 
 if (offset_y > (self.frame.size.height * (self.dataArray.count - 1))) {
     if (self.isLoading) {
         return;
     }
     NSLog(@"拉到底部了");
     
     self.isLoading = YES;
     [self.dataDelegate pullNewData]; //如果拉到了底部逆屡,則去拉取新數(shù)據(jù)
     return;
 }

 if (self.currentIndexOfImageView > self.dataArray.count - 1) {
     return;
 }

 //向下滑動(dòng)。
 if (offset_y > (self.frame.size.height * self.currentIndexOfImageView) && translatePoint.y < 0) {
     self.currentIndexOfImageView++;
     NSLog(@"lalalalalal");

     if (self.currentIndexOfImageView == self.dataArray.count) {
         return;
     }

     self.firstImageView.frame = self.secondImageView.frame;
     self.firstImageView.image = self.secondImageView.image;
     self.secondImageView.frame = self.thirdImageView.frame;
     self.firstImageView.image = self.secondImageView.image;
     self.secondImageView.image = self.thirdImageView.image;

     CGRect frame = self.thirdImageView.frame;
     frame.origin.y += self.frame.size.height;
     self.thirdImageView.frame = frame;
     self.thirdVideoModel = [self.dataArray objectAtIndex:self.currentIndexOfImageView];
     [self.thirdImageView sd_setImageWithURL:self.thirdVideoModel.imageURL];
 }
 
 if (offset_y < 0) {
     NSLog(@"已經(jīng)到頂部了");
     return;
 }
 
 //向上滑動(dòng)
 if (translatePoint.y > 0 && offset_y < self.secondImageView.frame.origin.y) {
     if (self.currentIndexOfImageView >= 3) {
         self.thirdImageView.frame = self.secondImageView.frame;
         self.thirdImageView.image = self.secondImageView.image;
         self.secondImageView.frame = self.firstImageView.frame;
         self.secondImageView.image = self.firstImageView.image;
         
         CGRect frame = self.firstImageView.frame;
         frame.origin.y -= self.frame.size.height;
         self.firstImageView.frame = frame;
         self.firstVideoModel = [self.dataArray objectAtIndex:self.currentIndexOfImageView - IMAGEVIEW_COUNT];
         [self.firstImageView sd_setImageWithURL:self.firstVideoModel.imageURL];
         
         self.currentIndexOfImageView--;
     }
 }
}

六. 邊播邊下載技術(shù)實(shí)現(xiàn)

工程中使用AVPlayer來實(shí)現(xiàn)視頻的播放灌危,在視頻播放過程中康二,會(huì)經(jīng)歷如下過程:

avplayerprocess.png

這些過程都是系統(tǒng)的類幫我們完成的。如果我們要實(shí)現(xiàn)邊下邊播勇蝙,就需要在數(shù)據(jù)請(qǐng)求的過程中沫勿,設(shè)置一個(gè)代理,截取請(qǐng)求味混,然后轉(zhuǎn)發(fā)产雹,收到服務(wù)器數(shù)據(jù)返回后,代理保存數(shù)據(jù)到本地翁锡,然后再把數(shù)據(jù)返回到播放器那邊蔓挖,如下所示:

avplayerprocess.png

AVURLAsset中的AVAssetResourceLoader就是負(fù)責(zé)數(shù)據(jù)加載的,我們只要遵守它的AVAssetResourceLoaderDelegate協(xié)議馆衔,就能設(shè)置一個(gè)代理瘟判。AVURLAsset加載數(shù)據(jù)的時(shí)候,都會(huì)調(diào)用到協(xié)議shouldWaitForLoadingOfRequestedResource方法角溃,我們通過這個(gè)方法取獲取到請(qǐng)求拷获,并轉(zhuǎn)發(fā)到網(wǎng)絡(luò)訪問模塊。具體流程可以分為以下步驟:

1.修改視頻的URL的scheme為系統(tǒng)無法識(shí)別的scheme减细,這樣AVURLAsset發(fā)出的請(qǐng)求才會(huì)跑到我們的代理匆瓜。

- (NSURL *)getSchemeVideoURL:(NSURL *)url
{
  NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
  components.scheme = @"streaming";
  return [components URL];
}

2.設(shè)置我們的代理。

[urlAsset.resourceLoader setDelegate:self.resourceLoader queue:dispatch_get_main_queue()];

3.轉(zhuǎn)發(fā)請(qǐng)求

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
  NSLog(@"dk----%@", loadingRequest);
  [self.loadingRequests addObject:loadingRequest];
  [self dealLoadingRequest:loadingRequest];
  
  return YES;
}

4.把請(qǐng)求返回給播放器


- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest {
  long long startOffset = dataRequest.requestedOffset;
  ......
  
  [dataRequest respondWithData:[filedata subdataWithRange:NSMakeRange((NSUInteger)startOffset, (NSUInteger)numberOfBytesToRespondWith)]]; //把本地存在的數(shù)據(jù)返回到播放器
  
  ....
 }

七. 參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末未蝌,一起剝皮案震驚了整個(gè)濱河市驮吱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萧吠,老刑警劉巖左冬,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異怎憋,居然都是意外死亡又碌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門绊袋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毕匀,“玉大人,你說我怎么就攤上這事癌别≡聿恚” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵展姐,是天一觀的道長(zhǎng)躁垛。 經(jīng)常有香客問我剖毯,道長(zhǎng),這世上最難降的妖魔是什么教馆? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任逊谋,我火速辦了婚禮,結(jié)果婚禮上土铺,老公的妹妹穿的比我還像新娘胶滋。我一直安慰自己,他們只是感情好悲敷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布究恤。 她就那樣靜靜地躺著,像睡著了一般后德。 火紅的嫁衣襯著肌膚如雪部宿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天瓢湃,我揣著相機(jī)與錄音理张,去河邊找鬼。 笑死绵患,一個(gè)胖子當(dāng)著我的面吹牛涯穷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播藏雏,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼作煌!你這毒婦竟也來了掘殴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤粟誓,失蹤者是張志新(化名)和其女友劉穎奏寨,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹰服,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡病瞳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悲酷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片套菜。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖设易,靈堂內(nèi)的尸體忽然破棺而出逗柴,到底是詐尸還是另有隱情,我是刑警寧澤顿肺,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布戏溺,位于F島的核電站渣蜗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏旷祸。R本人自食惡果不足惜耕拷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望托享。 院中可真熱鬧骚烧,春花似錦、人聲如沸嫌吠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辫诅。三九已至凭戴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炕矮,已是汗流浹背么夫。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肤视,地道東北人档痪。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像邢滑,于是被迫代替她去往敵國(guó)和親腐螟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348