APP在线抢答解决方案(RTC直播间抢答或者抢背唱歌)
前言
2018年下半年要說(shuō)最火的產(chǎn)品莫過(guò)于音遇了,上線(xiàn)短短一連個(gè)月,估值將近2億美金,他的產(chǎn)品也被各大公司所模仿借鑒,包括我們公司?!接下來(lái)我將說(shuō)說(shuō)這款A(yù)PP的直播間的解決方案,以供大家參考!
也算是對(duì)我上一份工作的一個(gè)總結(jié)吧!?
產(chǎn)品分析
a.音視頻處理
游戲直播間是一種在線(xiàn)推流和拉流的操作,目前主要的推流和拉流的方式有常見(jiàn)的RTMP和RTC兩種;
RTMP: 基于 TCP 的標(biāo)準(zhǔn)協(xié)議,與 CDN 架構(gòu)兼容,對(duì)客戶(hù)來(lái)說(shuō)在現(xiàn)有單向直播架構(gòu)上,接入成本比較低,但是缺點(diǎn)也多,回音、傳輸延時(shí)大、耗費(fèi)CPU等資源等等,但是便宜;
RTC:降低了音視頻通信的接入門(mén)檻,而且用戶(hù)體驗(yàn)比較好,最主要的是沒(méi)有延遲;缺點(diǎn)就是價(jià)格昂貴(如果你公司不差錢(qián)就當(dāng)我沒(méi)說(shuō)?);
但是如果是普通的視頻直播什么的使用RTMP也沒(méi)啥問(wèn)題,關(guān)鍵是這種搶答類(lèi)的產(chǎn)品對(duì)實(shí)時(shí)性要求比較高,肯定要選擇RTC了!
目前RTC如果使用第三方收費(fèi)都不便宜,我們公司的產(chǎn)品用的是七牛云存儲(chǔ)的RTC直播間,2019年第一版剛出來(lái),價(jià)格還很便宜?。
相關(guān):《七牛實(shí)時(shí)音視頻云》
b.指令下發(fā)
如果想讓前端與后端實(shí)時(shí)通訊,進(jìn)行數(shù)據(jù)傳輸和指令交互,就需要做socket長(zhǎng)連接,這樣即需要后臺(tái)開(kāi)發(fā)人員對(duì)服務(wù)器進(jìn)行配置還需要公司出資購(gòu)買(mǎi)新的服務(wù)器,一切為了省錢(qián)......
這樣我就想到了使用IM即使聊天來(lái)代替socket長(zhǎng)連接的實(shí)時(shí)交互,關(guān)鍵是在游戲直播間還必須用到IM聊天系統(tǒng);具體的實(shí)現(xiàn)思路是在游戲直播間由服務(wù)器扮演管理員的角色,對(duì)房間的所有人所在的群組發(fā)送管理員消息,當(dāng)然所以得管理員消息我們會(huì)進(jìn)行篩選和過(guò)濾,這樣就不會(huì)暫時(shí)在聊天界面上了,還有一些答題信息或者答題結(jié)果我們都可以以data或字典的形式放在自定義的字段里面,代碼如下:
WEAKBLOCK;[TXIMTools manager].getNewMessages = ^(TIMMessage *meg) {//[2019-03-18 16:03:00][status:2 sender=255762] [TIMTextElem=text:準(zhǔn)備好了嗎!快點(diǎn)開(kāi)始吧!]for (RAUserModel *model in self.userArray) {if([model.userId intValue] == [meg.sender intValue]){[weakSelf.chatView addOneTXIMChatMeg:meg number:model.number];break;}}}; - (void)getAdminOrder:(NSInteger)code dataDict:(NSDictionary *)dataDict{WEAKBLOCK;if(code == 2000){//游戲開(kāi)始 gameRoom_isStratGame = YES;[RAMatchStateView showRAReadyStartViewFinished:^{NSLog(@"游戲開(kāi)始");}];[self upLoadUserArrayWithGameRoom:dataDict code:code];[self.engine joinRoomWithToken:_currentUserModel.rtcToken userData:nil];self.topView.hidden = NO;self.likesBtn.hidden = NO;}else if (code == 2001){//游戲出題 gameRoom//...此處省略2000行代碼...}else if(code == 2002){//用戶(hù)搶答(誰(shuí)搶到了) "id", "number", "name", "gender", "type"}else if(code == 2003){//通知哪個(gè)用戶(hù)搶到 "id", "number", "name", "gender", "type"_answerUserInfors = dataDict;//答題者信息[self.subjectView hideRASubjectView];[self.robButton hideRARobButton];[self.matchStateView refreshAnswerStateView:GetChanceType number:dataDict[@"number"] name:dataDict[@"name"]];if([dataDict[@"id"] integerValue] == [_currentUserModel.userId integerValue]){//當(dāng)前用戶(hù)搶到了 等待答題[MBProgressHUD SHOWPrompttextNeedClose:@"等待答題···"];}}else if(code == 2004){//全軍覆沒(méi) 返回null[self.subjectView hideRASubjectView];[self.robButton hideRARobButton];[self.matchStateView refreshAnswerStateView:NoOneAnswerType number:nil name:nil];_answerUserInfors = nil;}else if(code == 2006){//答題成功了 返回null//...此處省略2000行代碼...}else if(code == 2007){//答題打錯(cuò)了 返回null//...此處省略2000行代碼...}else if(code == 2008){//游戲結(jié)束 gameRoom[self upLoadUserArrayWithGameRoom:dataDict code:code];[self.subjectView hideRASubjectView];[self.matchStateView hideAnswerStateView];[self gameOver];_answerUserInfors = nil;}else if(code == 2009){//加入房間 room[self analysisRoomDataDict:dataDict];}else if(code == 2010){//退出房間 room[self analysisRoomDataDict:dataDict];}else if(code == 2011){//邀請(qǐng)好友進(jìn)入房間 "id"(用戶(hù)ID), "number", "name", "type"(用戶(hù)類(lèi)型), "gameType", "gradeDesc", "articleType", "roomId"//在base處理}else if(code == 2012){//淘汰用戶(hù) 返回 "id", "number", "name" 用戶(hù)Id 用戶(hù)number 用戶(hù)名稱(chēng)NSString *number = [NSString stringWithFormat:@"%@",dataDict[@"number"]];NSString *name = dataDict[@"name"];[self.subjectView hideRASubjectView];[self.matchStateView refreshAnswerStateView:DieOutType number:number name:name];}else if(code == 2013){//等待下一題[self.subjectView hideRASubjectView];[self.matchStateView refreshAnswerStateView:AnswerNextQuestionType number:nil name:nil];[MBProgressHUD HIDEPrompttextByClose];_answerUserInfors = nil;}else if(code == 2014){//誰(shuí)接背 返回 "id", "number", "name" 用戶(hù)Id 用戶(hù)number 用戶(hù)名稱(chēng)//...此處省略2000行代碼...}else if(code == 2015){//會(huì) 進(jìn)入答題流程 返回 "id", "number", "name" 用戶(hù)Id 用戶(hù)number 用戶(hù)名稱(chēng)//保持狀態(tài)}else if(code == 2016){//不會(huì) 返回 "id", "number", "name" 用戶(hù)Id 用戶(hù)number 用戶(hù)名稱(chēng)//保持狀態(tài)}else if(code == 2018){//搶背開(kāi)始//...此處省略2000行代碼...}else if(code == 2019){//用戶(hù)答題指令 返回 "id", "number", "name" 用戶(hù)Id 用戶(hù)number 用戶(hù)名稱(chēng) 開(kāi)始答題[self answerAndStopAnswer:dataDict];}else if(code == 2020){//取消匹配 room//在WaitingGameRoomViewController界面處理UI[self analysisRoomDataDict:dataDict];}else if(code == 2021){//匹配中 room[self showMatchingView];}else if(code == 2022){//匹配成功 gameRoom[self upLoadUserArrayWithGameRoom:dataDict code:code];self.headView.hidden = YES;self.inviteBtn.hidden = YES;self.quickStartBtn.hidden = YES;}else if(code == 2023){//用戶(hù)準(zhǔn)備 返回 "id", "number", "name" 用戶(hù)Id 用戶(hù)number 用戶(hù)名稱(chēng)[self userReadyOrUnReady:dataDict[@"id"] isReady:YES];}else if(code == 2024){//用戶(hù)取消準(zhǔn)備 返回 "id", "number", "name" 用戶(hù)Id 用戶(hù)number 用戶(hù)名稱(chēng)[self userReadyOrUnReady:dataDict[@"id"] isReady:NO];}else if(code == 2025){//用戶(hù)送禮 giveUserNumber userNumber pic[self.giftListView addOneSentGiftMeg:dataDict];}else if(code == 2026){//用戶(hù)被踢出房間(只有被踢出的人才能收到這個(gè)消息) roomId - (void)kickoutUser:(NSString *)userId;[self.engine kickoutUser:_currentUserModel.userId];[ZFJReciteAlertView showAlertViewSureBtnWithTitle:@"您已被房主移出房間!" selectedIndex:^(NSInteger index) {[weakSelf leaveRoom];}];}else if(code == 2027){//開(kāi)始推流 返回答題用戶(hù) 返回 "id", "number", "name" 用戶(hù)Id 用戶(hù)number 用戶(hù)名稱(chēng)//只有答題者才可以推流if([dataDict[@"id"] integerValue] == [_currentUserModel.userId integerValue]){[self.engine publishAudio];}} }注明:以上只提供邏輯指令處理,不會(huì)給出詳細(xì)數(shù)據(jù)處理或者動(dòng)畫(huà)代碼!
語(yǔ)言識(shí)別
我們需要將用戶(hù)說(shuō)的話(huà)識(shí)別為文字(漢子或英語(yǔ)),然后與數(shù)據(jù)庫(kù)的答案進(jìn)行對(duì)照,然后給出得分。這里的語(yǔ)言識(shí)別當(dāng)然推薦使用科大訊飛了,比較赫赫有名的走在語(yǔ)言識(shí)別前端的技術(shù)(收費(fèi)高);但是我們產(chǎn)品用的是百度語(yǔ)言識(shí)別,為什么呢?免費(fèi)、免費(fèi)、免費(fèi),重要的事情我說(shuō)三遍!!!
一開(kāi)始想把百度語(yǔ)言識(shí)別的SDK直接集成在工程中,這樣識(shí)別的效率高、耗時(shí)短,但是,有兩點(diǎn)不好的地方,對(duì)于我這中優(yōu)化狂魔所不能忍的是:
a.語(yǔ)言識(shí)別的庫(kù)占用很大一部分內(nèi)存,是API的包增加了20多M;
b.我們還要對(duì)語(yǔ)音進(jìn)行保存,這樣在一邊識(shí)別的時(shí)候我們還要一邊錄音,導(dǎo)致的問(wèn)題就是,耗內(nèi)存資源;
所以經(jīng)過(guò)考慮,我們把百度語(yǔ)言識(shí)別交給服務(wù)端的小伙伴來(lái)搞,前端只需要錄音就可以了,然后把MP3文件直接傳給后端,后端小伙伴在指令里面給出結(jié)果!So easy!(媽媽再也不用擔(dān)心我的包太大了)?
音頻處理
這個(gè)功能我們需要經(jīng)常使用AVAudioRecorder,比如錄音、播放領(lǐng)讀、播放動(dòng)畫(huà)音效等等;AVAudioRecorder頻繁創(chuàng)建肯定會(huì)影響APP性能,所以我把AVAudioRecorder封裝起來(lái)丟進(jìn)單利里面;封裝的方法包含錄音和播放的功能,代碼如下(僅提供思路):
@interface RecordOperation : NSObject//-----------------------------錄音相關(guān)-----------------------------/**普通MP3錄音@return self*/ - (instancetype)init;/**開(kāi)始錄音*/ - (void)startRecord;/**結(jié)束錄音返回caf@param scuBlock caf回調(diào)音頻文件地址*/ - (void)stopRecord_Caf_ScuBlock:(void(^)(NSString *urlPath, CGFloat audioSeconds))scuBlock;/**獲取錄音聲波狀態(tài)@param averagePower 聲波值回調(diào)*/ - (void)getAveragePowerForChannel:(AveragePower)value;/**播放錄音的文件*/ - (void)playListenningRecognition;/**是否正在錄音*/ @property (nonatomic,assign) BOOL isRecording;//-----------------------------播放相關(guān)-----------------------------/**播放音頻文件@param urlStr 音頻文件地址@param duration 音頻文件總時(shí)長(zhǎng)@param playCompleted 播放完成回調(diào)*/ - (void)playVoiceBubbleWithUrlStr:(NSString *)urlStr duration:(VoiceInforsBlock)duration playCompleted:(PlayCompleted)playCompleted;/**獲取播放音頻文件信息@param currentTime 播放時(shí)間@param progress 進(jìn)度*/ - (void)getVoiceBubbleCurrentTime:(VoiceInforsBlock)currentTime progress:(VoiceInforsBlock)progress;/**停止播放音頻文件*/ - (void)stopPlayVoiceBubble;@end控件封裝
像這種交互性比較強(qiáng)的產(chǎn)品,各種題目信息或者答題狀態(tài)還有試圖動(dòng)畫(huà)也肯定必不可少了,這就需要我們把相同的控件和功能進(jìn)行打包封裝,以減少C的代碼,不會(huì)使Controller過(guò)于臃腫,也更利于代碼的刻度與賞心悅目的趕腳!
下面的代碼是題目狀態(tài)信息的過(guò)渡動(dòng)畫(huà),僅供參考:
@interface RAMatchStateView : UIView/**正在匹配初始化@return self*/ - (instancetype)initReadyToStartView;/**顯示匹配成功*/ - (void)showMatchCompletion;/**準(zhǔn)備開(kāi)始 1 2 3 GO@param finished 完成回調(diào)*/ + (void)showRAReadyStartViewFinished:(FinishCountDownBlock)finished;/**答題狀態(tài)初始化@param frame 坐標(biāo)@return self*/ - (instancetype)initAnswerStateView:(CGRect)frame;/**刷新控件答題狀態(tài)@param stateType 狀態(tài)類(lèi)型@param number 用戶(hù)ID@param name 用戶(hù)name*/ - (void)refreshAnswerStateView:(AnswerStateType)stateType number:(NSString *)number name:(NSString *)name;/**隱藏控件答題狀態(tài)*/ - (void)hideAnswerStateView;@end產(chǎn)品展示
結(jié)束語(yǔ)
歡迎各位大神補(bǔ)充!
?
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的APP在线抢答解决方案(RTC直播间抢答或者抢背唱歌)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 数据库的设计的六个阶段
- 下一篇: win10计算机配置在哪里打开,详细教您