又快到一年一度的雙十一了。淘寶直播一姐曾在去年雙十一,一個人賣出了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)主播端收穫答案
今年年初我花一個月的時間收錄整理了一套知識體系,如果有想法深入的系統化的去學習的,可以私信我【安卓】,我會把我收錄整理的資料都送給大家,幫助大家更快的進階。