文为声网 RTE 开发者社区作者投稿,作者为@arige
不妨大家想一下这个场景:
你走进一个咖啡厅,看到了一个美女或者帅哥,想要一个微信号,方便后续发展。
那我们要怎么做呢?
走到对方跟前,小声对他说:“你好,你长的很像我的前女友(前男友),可以加下你的微信吗?”。
对方有可能悄声的说,“我扫你”。
你这个时候你就需要把你的二维码展示给他。
当你们恋爱成功,求婚的时候,可能要找一个更加浪漫的环境,并且你们的对话希望给更多人看到、听到。
通过这个上面场景的想象,我们可以简单的确定一些基本的要素
为了满足上面提到得要求,我们需要以下功能:
如果要完整的实现一个完整的项目会花费比较多的时间,而我发现目前声网的 Demo 会提供简单的人物模型和素材,但是他们开放度较高,开发者可以根据需要使用自己定制化的模型素材,为了实现上述提到的需求,我只做了一些微小的修改,就满足了我们的需求。本次主要看下效果,为后续在生产环境中落地做一些技术上的储备。
以下是在 demo 上调整后显示出来的效果。
这是用 unity 来实现的一个咖啡厅的样子
这是一个服装店,我们可以给人物进行换装,这个也是由 unity 来实现的。
以上两个的接入都非常的方便,我们可以很好的对接 unity,如果需要的话,后续有自己的 unity 场景也可以快速接入。
这个是我自己测试的 1 vs 1 的视频测试效果
这个是我自己测试的将本地视频分享给对方的一个效果
想象一下,是不是可以和你的对象一起看剧了呢?
以上效果都是基于声网的 sdk 来实现的,如果你也想进行尝试或者使用,可以继续查看我的接入过程和中间遇到的问题以及解决方案。
我们既然使用了声网的 sdk,那注册一个声网的账号做一些声网的账号配置也很合理吧。
然后进入详细配置
生成 token,并将 appid、证书、token 复制保存。
注意!!!这里很重要,一定要看仔细,项目中会使用到!
至此,账号的准备已经完成了。
配置 id 一般情况下,这个文件是不会上传到远程仓库的,写到这里也主要是安全考虑。将在后台中申请的 appid、证书,复制后粘贴到/Android/local.properties 里面,如下:
配置 channel 将在后台生成 token 的时候,填写的 channel 给写到类中,如下
配置项目权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- 对于 Android 12.0 及以上设备,还需要添加如下权限: -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
注意:
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
需要动态申请权限,否则无法正常完成视频功能。
sdk 本身是不会去申请权限的,所以需要我们自己在合适的位置帮 sdk 申请好权限。
咖啡厅
做形象
服装店
这里是直接使用了声网给的效果,如果自己项目需要的话,可以去自己去调整。
其中换装和捏脸都需要 unity 上的一些修改和 native 与 unity 的通信。整个可以参考换装和捏脸
前面已经完成了整体项目的构建。并且已经把 unity 内容跑起来了,那么接下来我们看下,如果要实现 1vs1 的视频要怎么处理。为了方便处理,我单独写了一个页面来处理视频 1vs1,效果如下:
视频聊天
这个流程图,建议大家多看几遍,在我们遇到问题的时候,这个图会给我们启发,帮助我们解决问题。
详细流程:
该对象管理了整个的视频等场景,是一个非常核心的对象。
try {
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = getBaseContext();
config.mAppId = appId;
config.mEventHandler = mRtcEventHandler;
config.mAudioScenario = Constants.AudioScenario.getValue(Constants.AudioScenario.DEFAULT);
mRtcEngine = RtcEngine.create(config);
} catch (Exception e) {
throw new RuntimeException("Check the error.");
}
// 视频默认禁用,你需要调用 enableVideo 启用视频流。
mRtcEngine.enableVideo();
// 录音默认禁用,你需要调用 enableAudio 启用录音。
mRtcEngine.enableAudio();
// 开启本地视频预览。
mRtcEngine.startPreview();
FrameLayout container = findViewById(R.id.local_video_view_container);
// 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象。
SurfaceView surfaceView = new SurfaceView (getBaseContext());
container.addView(surfaceView);
// 将 SurfaceView 对象传入声网,以渲染本地视频。
mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, KeyCenter.RTC_UID));
ChannelMediaOptions options = new ChannelMediaOptions();
// 将用户角色设置为 BROADCASTER。
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// 视频通话场景下,设置频道场景为 BROADCASTING。
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
其中 clientRoleType 有两种,如果是 CLIENT_ROLE_BROADCASTER 就是可以播和收,如果是 CLIENT_ROLE_AUDIENCE 就只能收看,当前就成了主播模式。
// 使用临时 Token 加入频道。
// 你需要自行指定用户 ID,并确保其在频道内的唯一性。
int res = mRtcEngine.joinChannel(token, channelName, KeyCenter.RTC_UID, options);
if (res != 0)
{
// Usually happens with invalid parameters
// Error code description can be found at:
// en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
// cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
Log.e("video","join err:"+RtcEngine.getErrorDescription(Math.abs(res)));
}
joinChannel 方法有返回值,可以看到我们加入频道是否成功了,如果不成功的话,我们可以看下错误原因,并对照解决;如果成功了,就可以观察 IRtcEngineEventHandler 对象的回调方法,重点关注下 onError(int err) 和 onJoinChannelSuccess(String channel, int uid, int elapsed) 如果收到 onJoinChannelSuccess 方法的回调,我们就可以关注 onUserJoined(int uid, int elapsed) 方法,我们可以在这个方法里开始显示远端内容。
@Override
// 监听频道内的远端主播,获取主播的 uid 信息。
public void onUserJoined(int uid, int elapsed) {
Log.e(TAG, "onUserJoined->" + uid);
runOnUiThread(new Runnable() {
@Override
public void run() {
// 从 onUserJoined 回调获取 uid 后,调用 setupRemoteVideo,设置远端视频视图。
setupRemoteVideo(uid);
}
});
}
private void setupRemoteVideo(int uid) {
FrameLayout container = findViewById(R.id.remote_video_view_container);
SurfaceView surfaceView = new SurfaceView (getBaseContext());
surfaceView.setZOrderMediaOverlay(true);
container.addView(surfaceView);
mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
}
最终,远端的视频会显示在 remote_video_view_container 上。
同步本地播放给远端
将本地视频怎么同步给远端的用户呢?其实从本质上来讲,将本地摄像头和将本地播放的视频给远端用户看,对远端用户都是一样的,不一样的是本地用户给远端用户的数据源是哪个。一个是摄像头,一个是播放器。
FrameLayout container = findViewById(R.id.local_video_view_container);
// 创建一个 SurfaceView 对象,并将其作为 FrameLayout 的子对象。
SurfaceView surfaceView = new SurfaceView (getBaseContext());
container.addView(surfaceView);
// 将 SurfaceView 对象传入声网,以渲染本地视频。
VideoCanvas videoCanvas = new VideoCanvas(surfaceView, Constants.RENDER_MODE_HIDDEN, Constants.VIDEO_MIRROR_MODE_AUTO,
Constants.VIDEO_SOURCE_MEDIA_PLAYER, mediaPlayer.getMediaPlayerId(), KeyCenter.RTC_UID);
mRtcEngine.setupLocalVideo(videoCanvas);
int res = mRtcEngine.joinChannel(token, channelName, KeyCenter.RTC_UID, options);
if (res != 0)
{
// Usually happens with invalid parameters
// Error code description can be found at:
// en: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
// cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
Log.e("video","join err:"+RtcEngine.getErrorDescription(Math.abs(res)));
}else {
mediaPlayer = mRtcEngine.createMediaPlayer();
mediaPlayer.registerPlayerObserver(this);
mediaPlayer.open(MetaChatConstants.VIDEO_URL, 0);
}
3.监听 onPlayerStateChanged 回调并在 state 是 PLAYER_STATE_OPEN_COMPLETED 的时候执行 play 方法,代码如下:
public void onPlayerStateChanged(io.agora.mediaplayer.Constants.MediaPlayerState state, io.agora.mediaplayer.Constants.MediaPlayerError error) {
if(state == io.agora.mediaplayer.Constants.MediaPlayerState.PLAYER_STATE_OPEN_COMPLETED){
mediaPlayer.play();
}
}
至此,就完成了功能上的使用。
除了上面提到的功能外,声网还提供了一些其他的功能,在需要的时候可以直接使用,或者少量修改就可以用的。
比如说空间音效功能,该功能基于声学原理,模拟声音在不同空间环境中的传播、反射、吸收效果。可以为用户提供旅游中路人聊天声、海浪声、风声等,让用户更沉浸式体验
再比如说实时共赏影音功能。该功能可以几乎无延时的实现,远程观影、听歌等功能。甚至可以实现 k 歌的能力。
更多的功能期待大家一起发掘!