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