又快到一年一度的雙十一了。淘寶直播一姐曾在去年雙十一,一個人賣出了3.3億的銷售額,創造了行業的銷售神話。近兩年,很多電商平台開始關注起直播互動電商,希望在直播中,也可以增加互動,例如在直播過程中,抛出限量優惠商品,實時發送搶購的消息給觀衆。于是我們做了一個簡單的Demo。
Demo大緻的整體想法如下:以視訊直播為主的互動模型基礎上,結合語音轉寫功能進行設計,為主播擺脫Windows端繁瑣操作,實作快速發題的功能。主播通過語音輸入題目(問答題,答案隻有是和否),确認後将題目文本發送給所有房間内的觀衆,觀衆收到題目後App主動彈框給觀衆選擇結果。
1.1 功能拆解:
- 隻有主播有釋出題目入口。
- 需要ASR(Automatic Speech Recognition-語音識别)功能,有online實時翻譯和本地offline翻譯兩個方案。
- ASR結果需要主播确認。
- ASR結果主播确認後需要通知給所有非主播使用者。
- 非主播使用者收到題目資訊時需要主動彈窗,給使用者選擇結果。
1.2 方案确定:
- 為了確定ASR的準确性選擇了online實時翻譯,通過比對最終選擇搜狗知音開放平台。
- 題目資訊也是文本類型,可以借用群聊實時消息通道,給題目資訊前面加上特殊字元,非主播使用者收到消息時判斷是否是以特殊字元開始,如果是remove特殊字元并彈窗顯示題目資訊。特殊字元定義時可以考慮到擴充性,以後其它類似功能也可以通過該方案來實作。
2.1 視訊直播DEMO
一個簡單的視訊直播Demo按以下幾個步驟就可以實作了,可以找幾個Android裝置run一下看看效果,還是相當easy滴。
Step1 SDK內建
SDK還好支援maven依賴,在build.gradle的dependencies子產品中加一行就行:
dependencies {
...
implementation 'io.agora.rtc:full-sdk:2.8.1'
}
複制代碼
Step2 直播引擎建立
聲網SDK有個重要的類RtcEngine,負責直播功能管理,提供了上/下線、狀态監聽、音/視訊設定等比較豐富的Api,碰到問題時,首先查這個類就對了。建立引擎時APP_ID參數為聲網開發平台建立的應用id。
private RtcEngine mRtcEngine;
try {
mRtcEngine = RtcEngine.create(context, LiveDefine.APP_ID,
mRtcEventHandler);
mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_COMMUNICATION);
mRtcEngine.enableAudio(); // 開啟音頻功能
mRtcEngine.enableVideo(); // 開啟視訊功能
} catch (Exception e) {
e.printStackTrace();
}
複制代碼
Step3 直播View關聯
角色有主播和觀衆區分,關聯View時有些許差別。ANCHOR_UID為主播使用者id,主播端關聯View時為自己的使用者id,觀衆端關聯view時為觀看的主播的使用者id。使用者系統需要應用自己管理,聲網SDK不提供使用者管理。
SurfaceView surface = RtcEngine.CreateRendererView(this);
// 主播端View關聯
mRtcEngine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
mRtcEngine.enableLocalAudio(true); // 主播端需要打開本地音頻
mRtcEngine.setupLocalVideo(new VideoCanvas(surface,
VideoCanvas.RENDER_MODE_HIDDEN, ANCHOR_UID)); // 主播端設定的是本地video
mRtcEngine.startPreview(); //主播需要開啟視訊預覽
// 觀衆端View關聯
mRtcEngine.setClientRole(Constants.CLIENT_ROLE_AUDIENCE);
mRtcEngine.enableLocalAudio(false); // 觀衆端不需要打開本地音頻
mRtcEngine.setupRemoteVideo(new VideoCanvas(surface,
VideoCanvas.RENDER_MODE_HIDDEN, ANCHOR_UID)); // 觀衆端設定的是遠端即主播video
複制代碼
Step4 加入房間
加入房間時第一個參數token為目前登入賬戶對應的token,應用自己管理,測試時可從傳空。第二個參數為頻道id,也是由應用自己管理的。第三個參數為頻道名稱。最後一個參數為目前登入的賬戶id
mRtcEngine.joinChannel("", CHANNEL_ID, "CHANNEL_NAME", uid);
複制代碼
Step5 離開房間
// 主播端離開
mRtcEngine.setupLocalVideo(null);
mRtcEngine.stopPreview();
mRtcEngine.leaveChannel();
// 觀衆端離開
mRtcEngine.setupRemoteVideo(null);
mRtcEngine.leaveChannel();
複制代碼
2.2 消息功能
直播房間消息功能可以說是相對基礎而簡單的了,我們選用的是聲網實時資訊SDK,這是一個獨立的工具類SDK,聲網将實時消息功能解耦出來,可以給各個場景提供消息支援。群聊實時消息可參考如下步驟:
Step1 依賴配置
dependencies {
...
implementation 'io.agora.rtm:rtm-sdk:1.0.1'
}
複制代碼
Step2 消息引擎建立
// APP_ID同視訊互動SDK保持一緻即可
private RtmClient mRtmClient;
mRtmClient = RtmClient.createInstance(context, LiveDefine.APP_ID, listener);
複制代碼
Step3 房間消息初始化
建立一個消息頻道前需要調一次登入操作,第一個參數為應用賬戶token,第二個參數為賬戶辨別。
mRtmClient.login("", userId,
new ResultCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "rtmClient login success");
}
@Override
public void onFailure(ErrorInfo errorInfo) {
Log.d(TAG, "rtmClient login fail : " + errorInfo);
}
});
複制代碼
建立消息頻道,CHANNEL_ID是一個辨別,可以和直播頻道不一緻,但是建議保持一緻:
RtmChannel mRtmChannel;
RtmChannelListener rtmListener = new RtmChannelListener(){
@Override
public void onMessageReceived(RtmMessage var1, RtmChannelMember var2){
// 收到消息,自己發送的消息也會有該方法回調,可以通過RtmChannelMember判斷發送消息的人是不是自己,如果是不處理本次消息即可。
}
@Override
public void onMemberJoined(RtmChannelMember var1){
// 有使用者加入,可用來做使用者上線消息處理
}
@Override
public void onMemberLeft(RtmChannelMember var1){
// 有使用者離開,可用來做使用者離線消息處理
}
};
mRtmChannel = mRtmClient.createChannel(CHANNEL_ID,
rtmListener );;
複制代碼
Step4 發送消息
RtmMessage rtmMessage = mRtmClient.createMessage();
rtmMessage.setText(msg);
mRtmChannel.sendMessage(rtmMessage, callback);
複制代碼
Step5 退出消息頻道
可在退出直播房間時,調用該方法。
mRtmChannel.release();
複制代碼
2.3 線上語音翻譯
首先也是需要注冊賬戶并建立應用,詳見搜狗知音文檔中心,實作可參考如下步驟:
Step1 初始化
調用init方法初始化
// 以下資訊從知音平台申請獲得
private static final String BASE_URL = "api.zhiyin.sogou.com";
private static final String APP_ID = "";
private static final String APP_KEY = "";
private SogoSpeech mSogouSpeech;
private DefaultAudioSource mAudioSource;
private OnSogouAsrListener mListener;
public void init(Context context) {
ZhiyinInitInfo.Builder builder = new ZhiyinInitInfo.Builder();
ZhiyinInitInfo initInfo = builder.setBaseUrl(BASE_URL).setUuid(UUID).setAppid(APP_ID).setAppkey(APP_KEY).create();
SogoSpeech.initZhiyinInfo(context, initInfo);
SogoSpeechSettings settings = SogoSpeechSettings.shareInstance();
settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_AUDIO_CODING_INT,
1);
settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_VAD_ENABLE_BOOLEAN,
false);
settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_VAD_LONGMODE_BOOLEAN,
true); // 長時間ASR
settings.setProperty(Parameter.ASR_ONLINE_LANGUAGE_STRING,
ASRLanguageCode.CHINESE); // 也支援英文ASR ASRLanguageCode.ENGLIS
mSogouSpeech = new SogoSpeech(context);
mSogouSpeech.registerListener(mSpeechEventListener);
mAudioSource = new DefaultAudioSource(new AudioRecordDataProviderFactory(context));
mAudioSource.addAudioSourceListener(mAudioSourceListener);
}
private EventListener mSpeechEventListener = new EventListener() {
@Override
public void onEvent(String eventName, String param, byte[] data, int offset, int length, Object extra) {
if (TextUtils.equals(SpeechConstants.Message.MSG_ASR_ONLINE_LAST_RESULT,
eventName)) {
if (null != mListener) {
mListener.onSogouAsrResult(param);
}
stopTransform();
}
}
@Override
public void onError(String errorDomain, int errorCode, String errorDescription, Object extra) {
// 9002 使用者主動取消
if (9002 != errorCode && null != mListener) {
mListener.onSogouAsrResult("");
}
stopTransform();
}
};
private IAudioSourceListener mAudioSourceListener = new IAudioSourceListener() {
@Override
public void onBegin(IAudioSource iAudioSource) {
Log.d(TAG, "AudioSource onBegin");
mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_START, "", null, 0, 0);
}
@Override
public void onNewData(IAudioSource audioSource, Object dataArray, long packIndex, long sampleIndex, int flag) {
final short[] data = (short[]) dataArray;
mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_RECOGIZE, "", data, (int) packIndex, 0);
}
@Override
public void onEnd(IAudioSource audioSource, int status, Exception e, long sampleCount) {
Log.d(TAG, "AudioSource onEnd");
mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_STOP, "", null, 0, 0);
}
};
public interface OnSogouAsrListener {
void onSogouAsrResult(String result);
}
Step2 開始語音識别
public void startTransform(OnSogouAsrListener listener) {
mListener = listener;
mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_CREATE,
null, null, 0, 0);
new Thread(mAudioSource, "audioRecordSource").start();
}
Step3 停止語音識别
正常情況下不需要調用該方法,在EventListener 回調中已經調用過該方法了,為了確定狀态正常也可以在退出房間時手動調用一次。
public void stopTransform() {
mListener
= null;
if (null != mAudioSource) {
mAudioSource.stop();
}
}
最後秀一下Demo 實作的效果。
(1)主播端直播發題(語音轉文字):
(2)觀衆端答題
(3)主播端收獲答案
今年年初我花一個月的時間收錄整理了一套知識體系,如果有想法深入的系統化的去學習的,可以私信我【安卓】,我會把我收錄整理的資料都送給大家,幫助大家更快的進階。