做一個電商直播App,跟上這波雙十一

又快到一年一度的雙十一了。淘寶直播一姐曾在去年雙十一,一個人賣出了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)主播端直播發題(語音轉文字):

image.png

 


(2)觀衆端答題

image.png

 


(3)主播端收穫答案

image.png

今年年初我花一個月的時間收錄整理了一套知識體系,如果有想法深入的系統化的去學習的,可以私信我【安卓】,我會把我收錄整理的資料都送給大家,幫助大家更快的進階。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章