本文为声网 RTE 开发者社区作者投稿,作者为@arige

目标

1. 制定目标

不妨大家想一下这个场景:

你走进一个咖啡厅,看到了一个美女或者帅哥,想要一个微信号,方便后续发展。

那我们要怎么做呢?

走到对方跟前,小声对他说:“你好,你长的很像我的前女友(前男友),可以加下你的微信吗?”。

对方有可能悄声的说,“我扫你”。

你这个时候你就需要把你的二维码展示给他。

当你们恋爱成功,求婚的时候,可能要找一个更加浪漫的环境,并且你们的对话希望给更多人看到、听到。

通过这个上面场景的想象,我们可以简单的确定一些基本的要素

  1. 可以更改的场景 (咖啡厅和求婚现场)
  2. 自主塑造形象功能(毕竟外貌是一见钟情的原点)
  3. 单聊 (要微信号不能一次要一群人得呀。不然可能被当成流氓给打得哟)
  4. 文件共享 (把自己得二维码展示给对方,让对方加好友)
  5. 群聊、群视频 (把求婚现场给其他用户看)

2. 拆解目标

为了满足上面提到得要求,我们需要以下功能:

  1. 支持unity的方便接入。毕竟unity是一个非常好的3D引擎,如果可以方便接入得话,在后续得场景创建和人物创造上都可以有更大的扩展空间
  2. 1 vs 1的视频,或者 1 vs 多的直播模式。可以满足单聊和群聊的需求
  3. 同步本地播放的本地视频或者远端视频给其他用户。可以满足共享二维码的需求。

3. 效果预览

如果要完整的实现一个完整的项目会花费比较多的时间,而我发现目前声网的 Demo 会提供简单的人物模型和素材,但是他们开放度较高,开发者可以根据需要使用自己定制化的模型素材,为了实现上述提到的需求,我只做了一些微小的修改,就满足了我们的需求。本次主要看下效果,为后续在生产环境中落地做一些技术上的储备。

以下是在demo上调整后显示出来的效果。


这是用unity来实现的一个咖啡厅的样子


这是一个服装店,我们可以给人物进行换装,这个也是由unity来实现的。

以上两个的接入都非常的方便,我们可以很好的对接unity,如果需要的话,后续有自己的unity场景也可以快速接入。


这个是我自己测试的1 vs 1的视频测试效果

这个是我自己测试的将本地视频分享给对方的一个效果

想象一下,是不是可以和你的对象一起看剧了呢?

简单实现

以上效果都是基于声网的sdk来实现的,如果你也想进行尝试或者使用,可以继续查看我的接入过程和中间遇到的问题以及解决方案。

1. 账号准备

我们既然使用了声网的sdk,那注册一个声网的账号做一些声网的账号配置也很合理吧。

  1. 注册账号
  2. 完成实名
  3. 创建项目

  1. 配置项目

然后进入详细配置

生成token,并将appid、证书、token复制保存。
注意!!!这里很重要,一定要看仔细,项目中会使用到!

至此,账号的准备已经完成了。

2. 配置项目

  1. demo源码
  2. 通过声网官网客服,申请元宇宙 SDK、Unity工程文件、开发指南,同时申请开通使用权限,详情可访问 shengwang.cn
  3. 将sdk解压并配置到项目中 一定要注意目录,如果自己的项目中不存在当前的目录要自己创建

  1. 配置id一般情况下,这个文件是不会上传到远程仓库的,写到这里也主要是安全考虑。将在后台中申请的appid、证书,复制后粘贴到/Android/local.properties里面,如下:
  2. 配置channel 将在后台生成token的时候,填写的channel给写到类中,如下
  3. 配置项目权限
 <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申请好权限。

  1. 混淆配置


咖啡厅

做形象


服装店

这里是直接使用了声网给的效果,如果自己项目需要的话,可以去自己去调整。

其中换装和捏脸都需要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. 将本地播放的视频显示给远端用户


同步本地播放给远端

将本地视频怎么同步给远端的用户呢?其实从本质上来讲,将本地摄像头和将本地播放的视频给远端用户看,对远端用户都是一样的,不一样的是本地用户给远端用户的数据源是哪个。一个是摄像头,一个是播放器。

  1. 设置本地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);
  1. 在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歌的能力。

更多的功能期待大家一起发掘!

参考资料


RTE开发者社区
647 声望966 粉丝

RTE 开发者社区是聚焦实时互动领域的中立开发者社区。不止于纯粹的技术交流,我们相信开发者具备更加丰盈的个体价值。行业发展变革、开发者职涯发展、技术创业创新资源,我们将陪跑开发者,共享、共建、共成长。