做一个电商直播App,跟上这波双十一

又快到一年一度的双十一了。淘宝直播一姐曾在去年双十一,一个人卖出了3.3亿的销售额,创造了行业的销售神话。最近淘宝直播一哥李佳琦,也成为了热门话题。近两年,很多电商平台开始关注起直播互动电商。在直播中,也可以增加互动,例如在电商直播过程中,抛出限量优惠商品,以题目的形式实时发送抢购的消息给观众。于是我们做了一个简单的Demo,效果如视频所示。

为了让主播可以快速输入,我们还通过合作伙伴搜狗的技术,为应用增加了语音识别与转写功能。主播通过语音输入题目,能更方便地发送给观众。观众收到题目后,屏幕会显示弹框。
1.1 功能拆解

  • 需支持互动直播功能,且直播音视频与文字消息、题目可以同步到达观众端。
  • 需要支持文字消息、题目消息的发送,并能稳定支持海量高并发。
  • 需要语音识别功能,语音识别结果需要主播确认后通过消息系统发送。
  • 观众收到题目信息时需要主动弹窗,给用户选择结果。

1.2 实现方案

在这个场景下,首先要保证音视频直播与互动消息可以做到同步,也就是说,当主播说出“我的天啊~买它~!”的时候,购物链接、题目等形式的消息需要与这句话同时发送到观众端。那就就需要采用基于 UDP 传输的实时音视频互动方案。

另一方面,文字、题目等消息不仅需要做到实时,还需要保证能抗住高并发。所以在这里我们采用实时消息 SDK。实时消息 SDK在传输方面,采用分布式架构、多机房多路保活机制、智能优化路径,在其它节点失效时自动转移,选择最优节点路径传输,服务可靠。同时,弹性可伸缩架构,支持单频道百万并发,轻松应对直播答题、电商直播、大班课等高并发场景。同时,支持动态快速扩容,可以灵活应对用户场景的快速增长。

  • 通过声网 Agora 视频互动直播 SDK实现直播功能。
  • 通过搜狗知音开放平台来实现语音识别功能。
  • 题目信息也是一种实时消息,就像文字消息、弹幕一样,可用实时消息通道进行传输。在这里我们统一用声网 Agora 实时消息 SDK 来实现。

2.1 实现视频直播

一个简单的视频直播 Demo 按以下几个步骤就可以实现了,可以找几个 Android 设备run一下看看效果。

Step1 SDK集成
SDK 支持 maven 依赖,在 build.gradle 的 dependencies 模块中加一行就行:
image
Step2 直播引擎创建

声网SDK 有个重要的类——RtcEngine,负责直播功能管理,提供了上/下线、状态监听、音/视频设置等 API。创建引擎时,需要用到 APP_ID ,大家可以在console.agora.io -->项目管理页面下获取 。

image
Step3 直播View关联
在一个直播建中,参与者的角色有主播和观众,关联View时有些许区别。ANCHOR_UID为主播用户id,主播端关联View时为自己的用户id,观众端关联view时为观看的主播的用户id。
image
Step4 加入房间
加入房间时,参数token为当前登录账户对应的token,应用自己管理,测试时可以传null。第二个参数为频道id,也是由应用自己管理的。第三个参数为频道名称。最后一个参数为当前登录的账户id。
image
Step5 离开房间
image
2.2 消息功能
电商直播房间消息功能可以说是相对基础而简单的了,我们选用的是声网实时信息SDK,这是一个独立的工具类SDK,声网将实时消息功能解耦出来,可以给各个场景提供消息支持。群聊实时消息可参考如下步骤:

Step1 依赖配置
image

Step2 消息引擎创建
image
Step3 房间消息初始化
创建一个消息频道前需要调一次登录操作,第一个参数为应用账户token,第二个参数为账户标识。

image
创建消息频道,CHANNEL_ID是一个标识,可以和直播频道不一致,但是建议保持一致:
image Step4 发送消息

image
Step5 退出消息频道
可在退出直播房间时,调用该方法。
image
2.3 语音识别

首先也是需要注册账户并创建应用,详见搜狗知音文档中心,实现可参考如下步骤:

Step1 初始化
调用init方法初始化

1. `// 以下信息从知音平台申请获得`
2. `privatestaticfinalString BASE_URL = "api.zhiyin.sogou.com";`
3. `privatestaticfinalString APP_ID = "";`
4. `privatestaticfinalString APP_KEY = "";`
5. `privateSogoSpeech mSogouSpeech;`
6. `privateDefaultAudioSource mAudioSource;`
7. `privateOnSogouAsrListener mListener;`

8. `publicvoid init(Context context) {`
9. `ZhiyinInitInfo.Builder builder = newZhiyinInitInfo.Builder();`
10. `ZhiyinInitInfo initInfo = builder.setBaseUrl(BASE_URL).setUuid(UUID).setAppid(APP_ID).setAppkey(APP_KEY).create();`
11. `SogoSpeech.initZhiyinInfo(context, initInfo);`
1. `SogoSpeechSettings settings = SogoSpeechSettings.shareInstance();`
2. `    settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_AUDIO_CODING_INT,`
3. `1);`
4. `    settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_VAD_ENABLE_BOOLEAN,`
5. `false);`
6. `    settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_VAD_LONGMODE_BOOLEAN,`
7. `true); // 长时间ASR`
8. `    settings.setProperty(Parameter.ASR_ONLINE_LANGUAGE_STRING,`
9. `ASRLanguageCode.CHINESE); // 也支持英文ASR ASRLanguageCode.ENGLIS`

10. `    mSogouSpeech = newSogoSpeech(context);`
11. `    mSogouSpeech.registerListener(mSpeechEventListener);`

12. `    mAudioSource = newDefaultAudioSource(newAudioRecordDataProviderFactory(context));`
13. `    mAudioSource.addAudioSourceListener(mAudioSourceListener);`
14. `}`
1. `privateEventListener mSpeechEventListener = newEventListener() {`
2. `@Override`
3. `publicvoid onEvent(String eventName, String param, byte[] data, int offset, int length, Object extra) {`
4. `if(TextUtils.equals(SpeechConstants.Message.MSG_ASR_ONLINE_LAST_RESULT,`
5. `eventName)) {`
6. `if(null!= mListener) {`
7. `                mListener.onSogouAsrResult(param);`
8. `}`
9. `            stopTransform();`
10. `}`
11. `}`
1. `@Override`
2. `publicvoid onError(String errorDomain, int errorCode, String errorDescription, Object extra) {`
3. `// 9002 用户主动取消`
4. `if(9002!= errorCode && null!= mListener) {`
5. `            mListener.onSogouAsrResult("");`
6. `}`
7. `        stopTransform();`
8. `}`
9. `};`

10. `privateIAudioSourceListener mAudioSourceListener = newIAudioSourceListener() {`
11. `@Override`
12. `publicvoid onBegin(IAudioSource iAudioSource) {`
13. `Log.d(TAG, "AudioSource onBegin");`
14. `        mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_START, "", null, 0, 0);`
15. `}`
1. `@Override`
2. `publicvoid onNewData(IAudioSource audioSource, Object dataArray, long packIndex, long sampleIndex, int flag) {`
3. `finalshort[] data = (short[]) dataArray;`
4. `        mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_RECOGIZE, "", data, (int) packIndex, 0);`
5. `}`

6. `@Override`
7. `publicvoid onEnd(IAudioSource audioSource, int status, Exception e, long sampleCount) {`
8. `Log.d(TAG, "AudioSource onEnd");`
9. `        mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_STOP, "", null, 0, 0);`
10. `}`
11. `};`

12. `publicinterfaceOnSogouAsrListener{`
13. `void onSogouAsrResult(String result);`
14. `}`

Step2 开始语音识别
image
Step3 停止语音识别
正常情况下不需要调用该方法,在EventListener 回调中已经调用过该方法了,为了确保状态正常也可以在退出房间时,手动调用一次。
image
我们已经将这个 Demo上传至 Github,大家可以直接下载使用。
Android版本:https://github.com/AgoraIO-Community/Live-Shop/tree/master/Android
iOS版本:https://github.com/AgoraIO-Community/Live-Shop/tree/master/iOS

推荐阅读
作者信息
AgoraTechnicalTeam
TA 暂未填写个人简介
文章
142
相关专栏
SDK 教程
55 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。