目标
1. 制定目标
不妨大家想一下这个场景:
你走进一个咖啡厅,看到了一个美女或者帅哥,想要一个微信号,方便后续发展。
那我们要怎么做呢?
走到对方跟前,小声对他说:“你好,你长的很像我的前女友(前男友),可以加下你的微信吗?”。
对方有可能悄声的说,“我扫你”。
你这个时候你就需要把你的二维码展示给他。
当你们恋爱成功,求婚的时候,可能要找一个更加浪漫的环境,并且你们的对话希望给更多人看到、听到。
通过这个上面场景的想象,我们可以简单的确定一些基本的要素
- 可以更改的场景 (咖啡厅和求婚现场)
- 自主塑造形象功能(毕竟外貌是一见钟情的原点)
- 单聊 (要微信号不能一次要一群人得呀。不然可能被当成流氓给打得哟)
- 文件共享 (把自己得二维码展示给对方,让对方加好友)
- 群聊、群视频 (把求婚现场给其他用户看)
2. 拆解目标
为了满足上面提到得要求,我们需要以下功能:
- 支持unity的方便接入。毕竟unity是一个非常好的3D引擎,如果可以方便接入得话,在后续得场景创建和人物创造上都可以有更大的扩展空间
- 1 vs 1的视频,或者 1 vs 多的直播模式。可以满足单聊和群聊的需求
- 同步本地播放的本地视频或者远端视频给其他用户。可以满足共享二维码的需求。
3. 效果预览
如果要完整的实现一个完整的项目会花费比较多的时间,而我发现目前声网的 Demo 会提供简单的人物模型和素材,但是他们开放度较高,开发者可以根据需要使用自己定制化的模型素材,为了实现上述提到的需求,我只做了一些微小的修改,就满足了我们的需求。本次主要看下效果,为后续在生产环境中落地做一些技术上的储备。
以下是在demo上调整后显示出来的效果。
这是用unity来实现的一个咖啡厅的样子
这是一个服装店,我们可以给人物进行换装,这个也是由unity来实现的。
以上两个的接入都非常的方便,我们可以很好的对接unity,如果需要的话,后续有自己的unity场景也可以快速接入。
这个是我自己测试的1 vs 1的视频测试效果
这个是我自己测试的将本地视频分享给对方的一个效果
想象一下,是不是可以和你的对象一起看剧了呢?
简单实现
以上效果都是基于声网的sdk来实现的,如果你也想进行尝试或者使用,可以继续查看我的接入过程和中间遇到的问题以及解决方案。
1. 账号准备
我们既然使用了声网的sdk,那注册一个声网的账号做一些声网的账号配置也很合理吧。
- 注册账号
- 完成实名
- 创建项目
- 配置项目
然后进入详细配置
生成token,并将appid、证书、token复制保存。 注意!!!这里很重要,一定要看仔细,项目中会使用到!
至此,账号的准备已经完成了。
2. 配置项目
- demo源码
- 通过声网官网客服,申请元宇宙 SDK、Unity工程文件、开发指南,同时申请开通使用权限,详情可访问 shengwang.cn
- 将sdk解压并配置到项目中 一定要注意目录,如果自己的项目中不存在当前的目录要自己创建
-
配置id一般情况下,这个文件是不会上传到远程仓库的,写到这里也主要是安全考虑。将在后台中申请的appid、证书,复制后粘贴到/Android/local.properties里面,如下:
-
配置channel 将在后台生成token的时候,填写的channel给写到类中,如下
-
配置项目权限
注意:
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
需要动态申请权限,否则无法正常完成视频功能。
sdk本身是不会去申请权限的,所以需要我们自己在合适的位置帮sdk申请好权限。
- 混淆配置
咖啡厅
做形象
服装店
这里是直接使用了声网给的效果,如果自己项目需要的话,可以去自己去调整。
其中换装和捏脸都需要unity上的一些修改和native与unity的通信。整个可以参考换装和捏脸
3. 视频1vs1
前面已经完成了整体项目的构建。并且已经把unity内容跑起来了,那么接下来我们看下,如果要实现 1vs1的视频要怎么处理。为了方便处理,我单独写了一个页面来处理视频1vs1,效果如下:
视频聊天
这个流程图,建议大家多看几遍,在我们遇到问题的时候,这个图会给我们启发,帮助我们解决问题。
详细流程:
1. 创建RtcEngine对象
该对象管理了整个的视频等场景,是一个非常核心的对象。
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.");
}
2. 创建RtcEngine属性
// 视频默认禁用,你需要调用 enableVideo 启用视频流。
mRtcEngine.enableVideo();
// 录音默认禁用,你需要调用 enableAudio 启用录音。
mRtcEngine.enableAudio();
// 开启本地视频预览。
mRtcEngine.startPreview();
3. 将本地摄像头内容显示到local_video_view_container上
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));
4. 设置当前的模式
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就只能收看,当前就成了主播模式。
5. 加入频道
// 使用临时 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) 方法,我们可以在这个方法里开始显示远端内容。
6. 显示远端内容
@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 上。
4. 将本地播放的视频显示给远端用户
同步本地播放给远端
将本地视频怎么同步给远端的用户呢?其实从本质上来讲,将本地摄像头和将本地播放的视频给远端用户看,对远端用户都是一样的,不一样的是本地用户给远端用户的数据源是哪个。一个是摄像头,一个是播放器。
- 设置本地video的时候做下修改
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);是0的时候,调用如下方法:
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歌的能力。
更多的功能期待大家一起发掘!
参考资料
-
注册声网账号
-
声网官网
-
SDK下载
-
快速开始 – 实现音视频通话
-
媒体播放器
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net