iOS視頻直播筆記

最近在做視頻和直播業(yè)務(wù)洞翩,整理成文章和demo,記錄一下開發(fā)心得
因為直播已經(jīng)火了好幾年了岗喉,移動端的直播 的核心功能其實已經(jīng)很成熟了哼勇,大多集成一些第三方sdk或開源項目就可以實現(xiàn),但是直播涉及技術(shù)點多乖菱,比如播放器,IM即時通訊,彈幕,禮物等坡锡,如果有推流還要搭建推流端,難點在于這些技術(shù)相互關(guān)聯(lián)窒所,做到不卡頓鹉勒,流暢度好。
本文demoZBLiveRoom
包含 直播間吵取, 抖音短視頻禽额,廣告+正片,視頻歷史彈幕綁定,以后還會陸續(xù)更新
直播間

livegif.gif

抖音
douyingif.gif

廣告+正片
adgif.gif

視頻歷史彈幕綁定
danmugif.gif

播放器

首先說下 播放器的選擇脯倒,因為我們服務(wù)器選擇的阿里云 做直播推流相關(guān)服務(wù)实辑,我第一時間也選擇的阿里云播放器,嘗試集成了一下藻丢,就一字 坑剪撬,阿里云播放器,真的是難用悠反,看里面的代碼残黑,應(yīng)該是換了好幾個人維護,居然有注釋 這個屬性是干嘛的斋否,自己的人都看不懂梨水,我們怎么玩 。同時安卓同事也反饋茵臭,太難用了疫诽,我們果斷換。接下來研究了ijkplayer旦委,這個也是iOS用的比較廣泛的奇徒,很多坑前人基本都踩沒了,控制層也有很多出名的比如ZFPlayer結(jié)合ijkplayer
缨硝,正好我們app 直播和視頻 都需要支持逼龟。但是就一點ijkplayer 太大了,最新版500多M ,git根本傳不上去追葡,也找了很多方案,基本上就是把ijkplayer放在本地奕短,不傳git宜肉。這個在多人開發(fā)的團隊還是不太方便的。而且打完包確實大了不少翎碑,所以又舍棄了谬返。后來我又調(diào)研了其他一些播放器,都感覺不太滿意日杈,最后在同事的推薦下遣铝,找到了 SuperPlayer_iOS,是騰訊對自家的TXLiteAVSDK_Player 的一個封裝莉擒,還是比較滿意的,整個sdk大小100M多點酿炸,看打印的log ,TXLiteAVSDK_Player 底層也應(yīng)該是對ijkplayer的封裝涨冀,
SuperPlayer_iOS 當然也有缺點填硕,最不能讓人忍受的就是橫屏的問題,SuperPlayer_iOS的橫屏是一個假橫屏,用View做的方向改變扁眯,而控制器還是豎屏壮莹,會造成很多需求有問題,比如我們在橫屏?xí)r想彈出鍵盤姻檀,你會發(fā)現(xiàn)是豎屏鍵盤命满,很坑,所有如果你的項目對橫屏有類似業(yè)務(wù)的绣版,還是不要使用SuperPlayer_iOS胶台,推薦ZFPlayer,后期我們播放視頻業(yè)務(wù)已經(jīng)切換到了ZFPlayer

當然每個app僵娃,都是要單獨設(shè)計UI控制層的概作,SuperPlayer_iOS 雖然支持自定義控制層,但是有些地方的UI或控制,它還是無法自定義默怨,所以不能用cocoapods 去管理讯榕,pod install一下,所有更改恢復(fù)原樣了匙睹,當然我這個demo 還是用的 cocoapods集成的愚屁,如果如果SuperPlayer_iOS 的默認控制層,或自定義控制層痕檬,能滿足你的需求霎槐,直接pod 'SuperPlayer'就可以了

如果不能滿足手動復(fù)制了 SuperPlayer_iOS 所有代碼
pod 'TXLiteAVSDK_Player'
pod 'MMLayout'
//下面這倆個項目里已經(jīng)有,就不用添加了
//pod 'SDWebImage'
//pod 'AFNetworking'
創(chuàng)建播放器

#import <SuperPlayer/SuperPlayer.h>

    _playerView = [[SuperPlayerView alloc] init];
    _playerView.delegate = self;
    _playerView.playerConfig.enableLog=NO;
    self.playerFatherView = [[UIView alloc] init];
    self.playerFatherView.backgroundColor = [UIColor blackColor];
    [self.view addSubview:self.playerFatherView];
    [self.playerFatherView mas_makeConstraints:^(MASConstraintMaker *make) {
        if (@available(iOS 11.0, *)) {
            make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
        } else {
            make.top.mas_equalTo(20+self.navigationController.navigationBar.bounds.size.height);
        }
        make.leading.trailing.mas_equalTo(0);
        make.height.mas_equalTo(self.playerFatherView.mas_width).multipliedBy(9.0f/16.0f);// 這里寬高比16:9,可自定義寬高比
    }];
    _playerView.fatherView =_playerFatherView;// 設(shè)置父視圖

播放時建議使用真機梦谜,模擬器會有很多問題丘跌。
在播放器 播放時 一般都會有移動網(wǎng)絡(luò) 提示 ,如果是移動網(wǎng)絡(luò) 提示一下,如果是wifi 直接播放

 if ([self isNetworkWiFi]==NO) {
       //移動網(wǎng)絡(luò)提示ui
 }else{ 
    SuperPlayerModel *playerModel = [[SuperPlayerModel alloc] init];
    /**
     坑一
     模擬器播放 flv格式 畫面紅色唁桩,有聲音闭树,真機沒有問題。
     因為SuperPlayerViewConfig 內(nèi)的hwAcceleration 方法 荒澡,模擬器默認硬解碼為默認 NO造成的 报辱,
    坑二
    模擬器 播放mp4格式視頻 幀數(shù)很低 20-30fps,使用真機幀數(shù)恢復(fù)正常猜測還是解碼的問題单山,直播 基本57-60fps 使用真機正常
     */
    playerModel.videoURL=@"http://URL";
    [_playerView.coverImageView sd_setImageWithURL:[NSURL URLWithString:@"http://1252463788.vod2.myqcloud.com/e12fcc4dvodgzp1252463788/28742df34564972819219071568/4564972819209692959.jpeg"]];
    [_playerView playWithModel:playerModel];
}

移動端播放直播流格式 一般推薦用 flv的


4FCEED16-1B05-425A-BC7D-25D78A30A634.png

聊天室

聊天室 分兩部分吧
一碍现、IM的通信服務(wù),我們并沒有使用第三方服務(wù)米奸,長鏈接由自家服務(wù)器開發(fā)昼接,因為我們pc端也要做直播業(yè)務(wù),而pc端支持只支持WebSocket協(xié)議悴晰,iOS實現(xiàn)WebSocke通信辩棒,我們選擇了facebook的SocketRocket,SocketRocket非常完美,沒什么可說的一睁,唯一的缺點可能就是好幾年不更新了钻弄。

另外長鏈接的保活機制需要注意幾點者吁,大約邏輯就是
連接失敗窘俺,可以實現(xiàn)掉線自動重連
1.判斷當前網(wǎng)絡(luò)環(huán)境,如果斷網(wǎng)了就不要連了复凳,等待網(wǎng)絡(luò)到來瘤泪,在發(fā)起重連;
2.判斷調(diào)用層是否需要連接,例如用戶都沒在聊天界面育八,連接上去浪費流量

二对途、聊天室,參考了下面這兩位大神的文章
翻炒吧蛋滾飯:直播中聊天室踩過的坑以及我的填坑歷程
大怪猿:iOS直播間聊天室—圖文混排加載網(wǎng)絡(luò)圖片髓棋、
有面向?qū)ο箝_發(fā)实檀,面向過程開發(fā),面向bug開發(fā)按声,那么我就是面向大神開發(fā)
聊天室ui膳犹,是tableView搭建的
主要就是大量數(shù)據(jù)的時候,如何保證直播間流程签则,不卡頓须床,對整個項目的性能沒有影響。
歸納總結(jié)了幾個優(yōu)化重點:

  1. 添加數(shù)據(jù)時 渐裂,不要使用[self.tableView reloadData];豺旬,使用單行刷新,
    當數(shù)量大的時候柒凉,這個真的很明顯哈垢。
  2. 對于ui不同聊天數(shù)據(jù),要使用不同cell
  3. cell的高度緩存
  4. 在聊天數(shù)據(jù)大于一定量時扛拨,需要刪除前面的部分數(shù)據(jù),已保證不卡頓举塔,
    這個我做過實驗绑警,iphone 6在1400多條數(shù)據(jù)的時候在不斷的添加數(shù)據(jù),不管用哪種方式刷新tableView央渣,就會明顯的卡頓计盒,應(yīng)該內(nèi)存方面的問題了
  5. 使用緩存數(shù)據(jù)源,
    當接收到數(shù)據(jù)芽丹,不要直接刷新北启,而是給緩存數(shù)據(jù)源,當達到條件后,在把緩存數(shù)據(jù)給正式數(shù)據(jù)源
  6. 在適當?shù)臅r候刷新tableView咕村,
    比如我的播放器橫屏了场钉,比如我滑動聊天列表,并沒有在聊天室的最下邊懈涛,其實都是不用去刷新tableView的
  7. 無限刷新 改為固定時間刷新
    在聊天數(shù)據(jù)量非常大的時候逛万,比如1秒鐘好幾十條,那tableView就會刷新好幾十次批钠,這太可怕了宇植,所以可以使用固定時間刷新tableView,因為有緩存數(shù)據(jù)源埋心,我們可以加邏輯指郁,比如每1秒鐘把緩存數(shù)據(jù)源添加到正式數(shù)據(jù),并刷新tableView
    做了以上的優(yōu)化后拷呆,聊天室基本不會卡頓了

彈幕

彈幕使用了OCBarrage闲坎,這個真的是很優(yōu)秀的開源項目了。
以下是相關(guān)代碼

//懶加載
- (OCBarrageManager *)barrageManager{
    if (!_barrageManager) {
         _barrageManager = [[OCBarrageManager alloc] init];
        _barrageManager.renderView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    }
    return _barrageManager;
}
//添加到播放器上
 [self.playerView addSubview:self.barrageManager.renderView];
 [self.playerView sendSubviewToBack:self.barrageManager.renderView];//防止擋住控制層
 [self.barrageManager.renderView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.playerView);
        make.top.equalTo(@(20));
        make.bottom.equalTo(self.playerView.mas_bottom).offset(-20);
 }];
//配置每一條彈幕 并發(fā)送
- (void)sendbarrage:(NSString *)str{
     OCBarrageTextDescriptor *textDescriptor = [[OCBarrageTextDescriptor alloc] init];
     textDescriptor.text = str;
     textDescriptor.textColor = kRandomColor;
//    CGFloat bannerHeight = 185.0/2.0;
     textDescriptor.renderRange = NSMakeRange(0,100)
     textDescriptor.positionPriority = OCBarragePositionLow;
     textDescriptor.textFont = [UIFont systemFontOfSize:14];
     textDescriptor.strokeColor = [[UIColor blackColor] colorWithAlphaComponent:0.3];
     textDescriptor.strokeWidth = -1;
     textDescriptor.animationDuration = arc4random()%5 + 10;
     textDescriptor.barrageCellClass = [OCBarrageTextCell class];
    self.barrageManager.renderView.renderPositionStyle=OCBarrageRenderPositionIncrease;
     [self.barrageManager renderBarrageDescriptor:textDescriptor];
}

但是有一個業(yè)務(wù)需求洋腮,我們不止有直播箫柳,還有很多視頻,視頻播放歷史彈幕啥供,這個OCBarrage并沒有現(xiàn)成的方法悯恍,
iOS還有個開源彈幕BarrageRenderer,這個是有視頻時間綁定彈幕的功能伙狐,但是我試了試涮毫,有很多bug,最終還是放棄了贷屎。
作為面向大神開發(fā)的我罢防,今天要雄起了,自己實現(xiàn)一個彈幕隊列的功能

彈幕隊列

服務(wù)器下發(fā)歷史彈幕格式 基本大約是這樣的.彈幕的時間 和彈幕的文本內(nèi)容

[
    {
    "seconds": "1",//彈幕時間(秒)
    "barrage": "大家好",//彈幕內(nèi)容
    },
    {
    "seconds": "40",//彈幕時間(秒)
    "barrage": "風(fēng)力雨里唉侄,我在評論區(qū)等你",//彈幕內(nèi)容
    },
   {
    "seconds": "40",//彈幕時間(秒)
    "barrage": "這個內(nèi)容我喜歡",//彈幕內(nèi)容
    },
    {
    "seconds": "111",//彈幕時間(秒)
    "barrage": "我是andi",//彈幕內(nèi)容
    },
]

基本思路就是把 彈幕以彈幕時間為key 文本為Value 存在字典里咒吐,這個存還是有些講究的,可以看到上面在40秒的時候同時存在兩個彈幕属划,這種情況其實很常見恬叹,火的視頻每秒甚至10多條,所以存的時候彈幕時間為key同眯,而Value就需要是一個同一時間文本的數(shù)組绽昼, 之后在取也是取的數(shù)組。
取彈幕 有兩種方式

  1. 在視頻當前時間回調(diào)刃胛稀(推薦)
    這個是 視頻走到哪硅确,根據(jù)視頻的當前時間取對應(yīng)的彈幕目溉,播放和暫停也沒有操作。
  2. 使用計時器
    在視頻的開始播放時菱农,就開啟計時器缭付,每一秒獲取一次 當前視頻播放的時間,根據(jù)獲取的時間取對應(yīng)的彈幕大莫,暫停時需要關(guān)閉計時器蛉腌,播放需要開啟計時器,

下面使用ZFPlayer 播放視頻 并綁定歷史彈幕只厘,

self.barrageQueue=[[ZBBarrageQueue alloc]init];
 self.barrageQueue.delegate=self;
//加載彈幕數(shù)據(jù)
  [barbrageArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * _Nonnull stop) {
        ZBBarrageModel *model=[[ZBBarrageModel alloc]init];
        model.text=obj[@"barrage"];
        model.seconds=[obj[@"seconds"]integerValue];
        [listArray addObject:model];
  }];
  [self.barrageQueue loadBarrageList:listArray];

 __block NSInteger tempTime;
 self.player.playerPlayTimeChanged = ^(id<ZFPlayerMediaPlayback>  _Nonnull asset, NSTimeInterval currentTime, NSTimeInterval duration) {
        @strongify(self)
        //因為此回調(diào)是0.1秒一次烙丛,所以做了此判斷,
        tempTime=currentTime;
        if (self.currentTime!=tempTime) {
            //彈幕列隊 和 視頻時間綁定
            [self.barrageQueue startQueueWithCurrentTime:tempTime];
        }
        self.currentTime=tempTime;
  };
#pragma mark - 彈幕隊列代理
- (void)barrageQueueGetTextArray:(NSArray *)textArray{
    [textArray enumerateObjectsUsingBlock:^(NSString *text, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"第%ld元素 發(fā)送的彈幕 %@",idx,text);
        [self sendbarrage:text isMe:NO];
    }];
}

當然取彈幕隊列其實還有一些要完善的問題羔味,
1.比如滑動進度條回退視頻會重復(fù)加載彈幕河咽,而有時可能就回退幾秒鐘,當前彈幕還在屏幕顯示赋元,怎么去重忘蟹。

禮物

因為我們項目,目前并沒有禮物這個業(yè)務(wù)搁凸,所以并沒有深入研究媚值。demo禮物直接使用了大怪猿:iOS端直播間禮物模塊,感覺非常不錯护糖,有時間會仔細閱讀一下源碼

其他

有些功能還要說下

  1. 聊天輸入框褥芒,我的實現(xiàn)思路就是 監(jiān)聽鍵盤彈起和回收事件, 使用的是textView嫡良,因為有的業(yè)務(wù)需求輸入的字數(shù)很多锰扶,textView可以換行
  2. 橫屏彈鍵盤這個上面也提到 SuperPlayer_iOS是假橫屏,橫屏彈的依然是豎屏鍵盤寝受,可以使用ZFPlayer 這個是控制器也會橫屏坷牛,所以橫屏鍵盤沒有問題,唯一要注意的是要在AppDelegate實現(xiàn)下面的方法很澄,防止整個工程都會跟著手機方向橫屏
/// 在這里寫支持的旋轉(zhuǎn)方向京闰,為了防止橫屏方向,應(yīng)用啟動時候界面變?yōu)闄M屏模式
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    if (self.allowOrentitaionRotation) {
        return UIInterfaceOrientationMaskAllButUpsideDown;
    }
    return UIInterfaceOrientationMaskPortrait;
}

最后奉上本文demoZBLiveRoom

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甩苛,一起剝皮案震驚了整個濱河市蹂楣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浪藻,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乾翔,死亡現(xiàn)場離奇詭異爱葵,居然都是意外死亡施戴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門萌丈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赞哗,“玉大人,你說我怎么就攤上這事辆雾》舅瘢” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵度迂,是天一觀的道長藤乙。 經(jīng)常有香客問我,道長惭墓,這世上最難降的妖魔是什么坛梁? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮腊凶,結(jié)果婚禮上划咐,老公的妹妹穿的比我還像新娘。我一直安慰自己钧萍,他們只是感情好褐缠,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著风瘦,像睡著了一般队魏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弛秋,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天器躏,我揣著相機與錄音,去河邊找鬼蟹略。 笑死登失,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的挖炬。 我是一名探鬼主播揽浙,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼意敛!你這毒婦竟也來了馅巷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤草姻,失蹤者是張志新(化名)和其女友劉穎钓猬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撩独,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡敞曹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年账月,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澳迫。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡局齿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出橄登,到底是詐尸還是另有隱情抓歼,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布拢锹,位于F島的核電站谣妻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏面褐。R本人自食惡果不足惜拌禾,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望展哭。 院中可真熱鬧湃窍,春花似錦、人聲如沸匪傍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽役衡。三九已至茵休,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間手蝎,已是汗流浹背榕莺。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棵介,地道東北人钉鸯。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像邮辽,于是被迫代替她去往敵國和親唠雕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345