播放器和推流
首先說一下,直播中最最重要的元素缘缚,那就是視頻播放器了勾笆。視頻播放器的選擇,其實(shí)是非常多的桥滨,最著名的窝爪,非b站的IJKPlayer莫屬了弛车。其實(shí)現(xiàn)在很多三方的播放器,開發(fā)者在編寫的時(shí)候蒲每,都是按照系統(tǒng)的MPMoviePlayer的接口設(shè)計(jì)的纷跛,所以,你只要學(xué)會(huì)使用一個(gè)播放器邀杏,其他播放器的使用都會(huì)很輕松的上手贫奠。
附上兩個(gè)比較有參考價(jià)值的demo,一個(gè)是自定義IJKPlayer望蜡,進(jìn)度條唤崭、音量、亮度脖律,可以參考這個(gè)demo谢肾,對(duì)IJKPlayer進(jìn)行深度的個(gè)性化定制(其他播放器也適用),比如大部分播放器支持的屏幕手勢(shì)(滑動(dòng)調(diào)整音量状您、亮度勒叠、進(jìn)度等),另一個(gè)是ZFPlayer膏孟,這個(gè)播放器基于AVPlayer眯分,主要可以參考里面的橫豎平切換的處理,也可以直接拿來做普通的視頻播放器繼承在應(yīng)用中柒桑,很多功能都已經(jīng)做好弊决,用起來很方便。
關(guān)于直播的推流魁淳,目前來說最火的應(yīng)該是這個(gè)了LFLiveKit飘诗。具體我沒有使用過,但有一些個(gè)人仿寫項(xiàng)目都是IJKPlayer配合LFLiveKit完成的界逛。
下面推薦幾個(gè)個(gè)人仿寫的項(xiàng)目昆稿,可以參考下大部分直播中會(huì)出現(xiàn)的場(chǎng)景的處理策略。這個(gè)是仿映客的520Linkee息拜,這個(gè)是仿喵播的MiaowShow溉潭,這兩個(gè)都是市面上比較常見的個(gè)人手機(jī)端直播的典型實(shí)現(xiàn)方案。
至于我所使用的播放器和推流SDK少欺,因?yàn)槲覀兊闹辈シ?wù)是和金山云合作的喳瓣,所以兩個(gè)SDK都是用的金山云自家的SDK,他們的SDK更新頻率挺快的赞别,而且最新版已經(jīng)支持https了畏陕。但他們的SDK也存在一些bug,不過好在他們的每一版更新都會(huì)及時(shí)的進(jìn)行修復(fù)仿滔。
經(jīng)過對(duì)比了好多家的SDK demo(阿里惠毁、網(wǎng)易犹芹、騰訊、七牛等)后仁讨,你會(huì)發(fā)現(xiàn)金山的SDK demo是寫的最完善的羽莺,推流端你直接拿過來給個(gè)推流地址就可以推了实昨,包括美顏洞豁、碼率、編碼等等荒给,都在demo上有選項(xiàng)可供設(shè)置丈挟,你只要在開發(fā)的時(shí)候,對(duì)這些功能重新設(shè)計(jì)下UI就好了志电。播放器demo曙咽、推流demo,建議在使用的過程中挑辆,多跟進(jìn)他們的更新release例朱,你會(huì)發(fā)現(xiàn)他們每次更新都會(huì)優(yōu)化很多功能、修復(fù)很多bug(不像友盟鱼蝉,每次更新都有新bugH鬣汀!魁亦!氣人S媪ァ!=嗄巍)间唉。
聊天
既然大家都在看直播,互動(dòng)肯定也少不了利术,直播聊天室就必須要有呈野。我們用的是融云,因?yàn)槿谠频男麄骱涂诒疾诲e(cuò)印叁,所以就選擇了融云被冒,而且也是好多直播服務(wù)商的合作伙伴,所以可以放心使用喉钢。其他的還有環(huán)信和野狗姆打,環(huán)信的控制臺(tái)和文檔,不如融云友好肠虽,野狗的沒有試過幔戏,個(gè)人建議使用融云。而且融云官網(wǎng)有集成了播放器税课、聊天的直播間demo可以參考闲延,里面帶了一個(gè)香港某電視臺(tái)的直播流痊剖,可以用來測(cè)試用來rtmp://live.hkstv.hk.lxdns.com/live/hks。
然后聊天中的聊天列表的處理垒玲,可以參考我的這篇簡(jiǎn)書來處理陆馁,以優(yōu)化性能http://www.reibang.com/p/518e9c169274。
這里有一點(diǎn)需要注意合愈,在一個(gè)controller中叮贩,將當(dāng)前controller設(shè)置為融云的消息接收代理,就可以接收融云消息了佛析。
[[RCIMClient sharedRCIMClient]setReceiveMessageDelegate:selfobject:nil];
在頁面dealloc中不要只調(diào)用 [RCIMClient sharedRCIMClient] quitChatRoom 退出直播間就覺得沒事了益老,因?yàn)橥顺鲋辈ラg是異步的,可能在當(dāng)前controller dealloc后才會(huì)退出寸莫,如果在這段時(shí)間收到新的消息捺萌,[RCIMClient sharedRCIMClient]就會(huì)因?yàn)閐elegate釋放了而導(dǎo)致崩潰,所以要在當(dāng)前controller的dealloc中設(shè)置消息接收代理為nil膘茎。
[[RCIMClient sharedRCIMClient]setReceiveMessageDelegate:nilobject:nil];
點(diǎn)贊動(dòng)畫
點(diǎn)贊動(dòng)畫可以參考這個(gè)https://github.com/singer1026/DMHeartFlyAnimation桃纯,主要通過CAKeyFrameAnimation和UIBezierPath完成,也可以自行修改代碼修改動(dòng)畫軌跡披坏、替換點(diǎn)贊圖片等态坦。
彈幕
彈幕建議使用BarrageRenderer,性能不錯(cuò)刮萌,git主頁的介紹驮配,就能讓你很簡(jiǎn)單的上手使用,但如果你要做歷史消息的彈幕和即時(shí)消息結(jié)合的彈幕着茸,建議歷史彈幕的遍歷以及時(shí)間軸綁定壮锻,還是自己寫比較好,因?yàn)檫@個(gè)庫(kù)的redisplay以及綁定時(shí)間軸方法涮阔,在與即時(shí)消息結(jié)合的時(shí)候猜绣,彈幕的展示可能會(huì)有重復(fù)出現(xiàn)多次的現(xiàn)象。
網(wǎng)絡(luò)切換
直播中我們要考慮用戶的當(dāng)前網(wǎng)絡(luò)狀態(tài)敬特,移動(dòng)網(wǎng)絡(luò)幫他停止播放掰邢,或者切換到wifi的時(shí)候,幫他重連伟阔,以減少流量的耗費(fèi)辣之。網(wǎng)絡(luò)的變化主要通過兩種方式判斷,一種是Reachability皱炉,另一種是獲取狀態(tài)欄上的網(wǎng)絡(luò)狀態(tài)怀估。
Reachability寫在AppDelegate中,在網(wǎng)絡(luò)狀態(tài)變化的時(shí)候,block中的代碼就會(huì)被調(diào)用多搀,你想把網(wǎng)絡(luò)變化的消息發(fā)送給直播頁面歧蕉,直接用通知中心就可以,然后Reachability建議使用AFNetworking的康铭,因?yàn)橹坝形恼抡fReachability庫(kù)可能會(huì)引起不支持ipv6導(dǎo)致審核被拒惯退,我們項(xiàng)目中用的AFNetworking中的Reachability,沒有問題:
- (void)monitorNetworking {? ? AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];? ? ? ? [mgr setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {switch(status) {? ? ? ? ? ? ? ? wifi網(wǎng)絡(luò)break;? ? ? ? ? ? ? ? 移動(dòng)網(wǎng)絡(luò)break;caseAFNetworkReachabilityStatusNotReachable:? ? ? ? ? ? ? ? 無網(wǎng)絡(luò)break;caseAFNetworkReachabilityStatusUnknown:? ? ? ? ? ? ? ? 未知網(wǎng)絡(luò)break;default:break;? ? ? ? }? ? }];//開始監(jiān)控[mgr startMonitoring];}
獲取狀態(tài)欄網(wǎng)絡(luò)狀態(tài)从藤,有人說在狀態(tài)欄隱藏的頁面催跪,沒法獲取網(wǎng)絡(luò)狀態(tài),實(shí)測(cè)是可以獲取的呛哟,方法里面有我寫的枚舉叠荠,替換下就好了:
- (NSString*)getCurrentNetWork {NSArray*subviews = [[[[UIApplicationsharedApplication] valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews];for(idchildinsubviews) {if([child isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {//獲取到狀態(tài)欄碼intnetworkType = [[child valueForKeyPath:@"dataNetworkType"] intValue];switch(networkType) {case0: {//? ? ? ? ? ? ? ? ? ? states = NetworkStatesNone;returnCurrentNetWorkNone;? ? ? ? ? ? ? ? }break;case1: {//? ? ? ? ? ? ? ? ? ? states = NetworkStates2G;returnCurrentNetWorkMobile;? ? ? ? ? ? ? ? }break;case2: {//? ? ? ? ? ? ? ? ? ? states = NetworkStates3G;returnCurrentNetWorkMobile;? ? ? ? ? ? ? ? }break;case3: {//? ? ? ? ? ? ? ? ? ? states = NetworkStates4G;returnCurrentNetWorkMobile;? ? ? ? ? ? ? ? }break;case5: {//? ? ? ? ? ? ? ? ? ? states = NetworkStatesWIFI;returnCurrentNetWorkWifi;? ? ? ? ? ? ? ? }break;default: {returnCurrentNetWorkNone;? ? ? ? ? ? ? ? }break;? ? ? ? ? ? }? ? ? ? }? ? }returnCurrentNetWorkNone;}