又快到一年一度的双十一了。淘宝直播一姐曾在去年双十一,一个人卖出了 3.3 亿的销售额,创造了行业的销售神话。最近淘宝直播一哥李佳琦,也成为了热门话题。近两年,很多电商平台开始关注起直播互动电商。在直播中,也可以增加互动,例如在直播过程中,抛出限量优惠商品,以题目的形式实时发送抢购的消息给观众。于是我们做了一个简单的 Demo,效果如视频所示。
为了让主播可以快速输入,我们还通过合作伙伴搜狗的技术,为应用增加了语音识别与转写功能。主播通过语音输入题目,能更方便地发送给观众。观众收到题目后,屏幕会显示弹框。
1.1 功能拆解
需支持互动直播功能,且直播音视频与文字消息、题目可以同步到达观众端。
需要支持文字消息、题目消息的发送,并能稳定支持海量高并发。
需要语音识别功能,语音识别结果需要主播确认后通过消息系统发送。
观众收到题目信息时需要主动弹窗,给用户选择结果。
1.2 实现方案
在这个场景下,首先要保证音视频直播与互动消息可以做到同步,也就是说,当主播说出“我的天啊~买它~!”的时候,购物链接、题目等形式的消息需要与这句话同时发送到观众端。那就就需要采用基于 UDP 传输的实时音视频互动方案。
另一方面,文字、题目等消息不仅需要做到实时,还需要保证能抗住高并发。所以在这里我们采用实时消息 SDK。实时消息 SDK 在传输方面,采用分布式架构、多机房多路保活机制、智能优化路径,在其它节点失效时自动转移,选择最优节点路径传输,服务可靠。同时,弹性可伸缩架构,支持单频道百万并发,轻松应对直播答题、电商互动直播、大班课等高并发场景。同时,支持动态快速扩容,可以灵活应对用户场景的快速增长。
2.1 实现视频直播
一个简单的视频直播 Demo 按以下几个步骤就可以实现了,可以找几个 Android 设备 run 一下看看效果。
Step1 SDK 集成
SDK 支持 maven 依赖,在 build.gradle 的 dependencies 模块中加一行就行:
dependencies {
...
implementation 'io.agora.rtc:full-sdk:2.8.1'
}
复制代码
Step2 直播引擎创建
声⽹SDK 有个重要的类——RtcEngine,负责直播功能管理,提供了上/下线、状态监听、⾳/视频设置等 API。创建引擎时,需要⽤到 APP_ID ,⼤家可以在 console.agora.io -->项⽬管理⻚⾯下获取 。
privateRtcEngine 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。
SurfaceView surface = RtcEngine.CreateRendererView(this);
// 主播端View关联
mRtcEngine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
mRtcEngine.enableLocalAudio(true); // 主播端需要打开本地⾳频
mRtcEngine.setupLocalVideo(newVideoCanvas(surface,
VideoCanvas.RENDER_MODE_HIDDEN, ANCHOR_UID)); // 主播端设置的是本地video
mRtcEngine.startPreview(); //主播需要开启视频预览
// 观众端View关联
mRtcEngine.setClientRole(Constants.CLIENT_ROLE_AUDIENCE);
mRtcEngine.enableLocalAudio(false); // 观众端不需要打开本地⾳频
mRtcEngine.setupRemoteVideo(newVideoCanvas(surface,
VideoCanvas.RENDER_MODE_HIDDEN, ANCHOR_UID));
//观众端设置的是远端即主播video
复制代码
Step4 加⼊房间
加⼊房间时,参数 token 为当前登录账户对应的 token,应⽤⾃⼰管理,测试时可以传 null。第⼆个参数为频道 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保持⼀致即可
privateRtmClient mRtmClient;
mRtmClient = RtmClient.createInstance(context, LiveDefine.APP_ID, listener);
复制代码
Step3 房间消息初始化
创建⼀个消息频道前需要调⼀次登录操作,第⼀个参数为应⽤账户 token,第⼆个参数为账户标识。
mRtmClient.login("", userId,
newResultCallback<Void>() {
@Override
publicvoid onSuccess(Void aVoid) {
Log.d(TAG, "rtmClient login success");
}
@Override
publicvoid onFailure(ErrorInfo errorInfo) {
Log.d(TAG, "rtmClient login fail : "+ errorInfo);
}
});
复制代码
创建消息频道,CHANNEL_ID 是⼀个标识,可以和直播频道不⼀致,但是建议保持⼀致:
Step4 发送消息
RtmMessage rtmMessage = mRtmClient.createMessage();
rtmMessage.setText(msg);
mRtmChannel.sendMessage(rtmMessage, callback);
复制代码
Step5 退出消息频道
可在退出直播房间时,调⽤该⽅法。
2.3 语⾳识别
⾸先也是需要注册账户并创建应⽤,详⻅搜狗知⾳⽂档中⼼,实现可参考如下步骤:
Step1 初始化
调⽤init⽅法初始化
// 以下信息从知⾳平台申请获得
privatestaticfinalString BASE_URL = "api.zhiyin.sogou.com";
privatestaticfinalString APP_ID = "";
privatestaticfinalString APP_KEY = "";
privateSogoSpeech mSogouSpeech;
privateDefaultAudioSource mAudioSource;
privateOnSogouAsrListener mListener;
publicvoid init(Context context) {
ZhiyinInitInfo.Builder builder = newZhiyinInitInfo.Builder();
ZhiyinInitInfo initInfo = builder.setBaseUrl(BASE_URL).setUuid(UUID).setAppid(APP_I
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 = newSogoSpeech(context);
mSogouSpeech.registerListener(mSpeechEventListener);
mAudioSource = newDefaultAudioSource(newAudioRecordDataProviderFactory(context)
mAudioSource.addAudioSourceListener(mAudioSourceListener);
}
privateEventListener mSpeechEventListener = newEventListener() {
@Override
publicvoid onEvent(String eventName, String param, byte[] data, int offset, int len
if(TextUtils.equals(SpeechConstants.Message.MSG_ASR_ONLINE_LAST_RESULT,
eventName)) {
if(null!= mListener) {
mListener.onSogouAsrResult(param);
}
stopTransform();
}
}
@Override
publicvoid onError(String errorDomain, int errorCode, String errorDescription, Obje
// 9002 ⽤户主动取消
if(9002!= errorCode && null!= mListener) {
mListener.onSogouAsrResult("");
}
stopTransform();
}
};
privateIAudioSourceListener mAudioSourceListener = newIAudioSourceListener() {
@Override
publicvoid onBegin(IAudioSource iAudioSource) {
Log.d(TAG, "AudioSource onBegin");
mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_START, "", null, 0, 0)
}
@Override
publicvoid onNewData(IAudioSource audioSource, Object dataArray, long packIndex, lo
finalshort[] data = (short[]) dataArray;
mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_RECOGIZE, "", data, (i
}
@Override
publicvoid onEnd(IAudioSource audioSource, int status, Exception e, long sampleCoun
Log.d(TAG, "AudioSource onEnd");
mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_STOP, "", null, 0, 0);
}
};
publicinterfaceOnSogouAsrListener{
void onSogouAsrResult(String result);
}
复制代码
Step2 开始语⾳识别
publicvoid startTransform(OnSogouAsrListener listener) {
mListener = listener;
mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_CREATE,
null, null, 0, 0);
newThread(mAudioSource, "audioRecordSource").start();
}
复制代码
Step3 停⽌语⾳识别
正常情况下不需要调⽤该⽅法,在 EventListener 回调中已经调⽤过该⽅法了,为了确保状态正常也可以
在退出房间时,⼿动调⽤⼀次。
publicvoid stopTransform() {
mListener
= null;
if(null!= mAudioSource) {
mAudioSource.stop();
}
}
复制代码
我们已经将这个 Demo 上传⾄ Github,⼤家可以直接下载使⽤。
Android 版本:https://github.com/AgoraIO-Community/Live-Shop/tree/master/Android
iOS 版本:https://github.com/AgoraIO-Community/Live-Shop/tree/master/iOS
本文转载自公众号声网 Agora(ID:shengwang-agora)。
原文链接:
https://mp.weixin.qq.com/s/I0nhZiOTNqu8_CY4X5FzSA
评论