场景介绍
在网络会议、双人视频通话等场景时,将手机横屏、竖屏放置场景下,实现本地和远端都可以看到正常的画面效果。
效果演示
当左边手机进行旋转时,即进行横屏推流,右边手机的小画面订阅到的远端流,动态调整view进行适配,避免出现黑边;
当两端手机都进行旋转时,两端都进行横屏推流,各自订阅的远端流画面进行动态调整view;
无论如何旋转手机,两端看到的画面都是正的。
(大画面:本地摄像头; 小画面:远端流)
实现逻辑
推流端
1)开启 SDK 重力感应,默认就是开启的,如果关闭了,请调用接口打开 SDK 重力感应
2)监听手机旋转角度
3)根据不同的旋转角度,设置视频编码参数,即横屏/竖屏编码
4)发送 SEI 消息,告知房间内其他用户,当前是横屏还是竖屏
5)根据不同的旋转角度,旋转自己订阅的远端流的画面
6)根据不同的旋转角度,来调整 activity 为横屏或竖屏
拉流端
1)收到远端用户的第一帧视频,根据宽高数据,调整渲染远端流的 view 宽高,避免小窗口出现黑边
2)当推流端旋转手机时,可以收到发送的 SEI 消息,根据传递过来的横屏或竖屏,调整渲染远端流的 view 宽高,避免出现黑边
代码逻辑
1)TRTC SDK 版本号
这里可以指定使用 9.5.11347 版本的 SDK,体验下效果。
SDK 发布日志历史:参考文档
代码语言:javascript复制com.tencent.liteav:LiteAVSDK_TRTC:9.5.11347
2)修改清单文件
android:configChanges 避免重新启动 activity
代码语言:javascript复制<activity
android:name="com.tencent.trtc.videocall.VideoCallingActivity"
android:configChanges="orientation|keyboard|layoutDirection|screenSize"
android:screenOrientation="portrait"
/>
3)开启重力感应
setGSensorMode 设置重力感应接口,默认开启,默认值为 TRTCGSensorMode_UIAutoLayout
代码语言:javascript复制mTRTCCloud.setGSensorMode(TRTCCloudDef.TRTC_GSENSOR_MODE_UIAUTOLAYOUT);
4)监听手机的旋转
当手机旋转时,如:90度、180度、270度时,需要将 activity 调整设置对应的 横屏或竖屏
使用 OrientationEventListener 实时监听手机的旋转角度
代码语言:javascript复制/**
* 获取到手机旋转的角度,取四个方向:0、90、180、270度
* @param orientation 0、90、180、270度
*/
@Override
public void onOrientationChanged(int orientation) {
// 手机平放,获取不到旋转角度
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
return;
}
// 获取手机旋转角度 : 0
if (orientation > 350 || orientation < 10) {
// 在当前旋转角度下,仅调用一次,切换角度后再去调用
if (!isCall(0)) {
// 根据旋转的角度,调整推流编码、发送SEI、调整渲染的view、activity方向
adjustVideoEnc(0);
}
// 获取手机旋转角度 : 90
} else if (orientation > 80 && orientation < 100) {
// 在当前旋转角度下,仅调用一次,切换角度后再去调用
if (!isCall(90)) {
// 根据旋转的角度,调整推流编码、发送SEI、调整渲染的view、activity方向
adjustVideoEnc(90);
}
// 获取手机旋转角度 : 180
} else if (orientation > 170 && orientation < 190) {
// 在当前旋转角度下,仅调用一次,切换角度后再去调用
if (!isCall(180)) {
// 根据旋转的角度,调整推流编码、发送SEI、调整渲染的view、activity方向
adjustVideoEnc(180);
}
// 获取手机旋转角度 : 270
} else if (orientation > 260 && orientation < 280) {
// 在当前旋转角度下,仅调用一次,切换角度后再去调用
if (!isCall(270)) {
// 根据旋转的角度,调整推流编码、发送SEI、调整渲染的view、activity方向
adjustVideoEnc(270);
}
}
}
手机的旋转角度事件,非常频繁,避免重复调用,需要控制下请求:
(这里仅是示例,根据实际需要进行调整逻辑!)
代码语言:javascript复制/**
* 在指定旋转角度,是否已经操作了,避免连续在同一个旋转角度操作多次
* @param mOrientation 0、90、180、270度
* @return true: 之前一次已经操作过了,不要再次操作了; false:之前一次没操作过,你去操作吧
*/
public boolean isCall(int mOrientation) {
Boolean flag = mRotationCall.get(mOrientation);
if (flag == null) {
flag = false;
}
switch (mOrientation) {
case 0:
mRotationCall.put(0, true);
mRotationCall.put(90, false);
mRotationCall.put(180, false);
mRotationCall.put(270, false);
break;
case 90:
mRotationCall.put(0, false);
mRotationCall.put(90, true);
mRotationCall.put(180, false);
mRotationCall.put(270, false);
break;
case 180:
mRotationCall.put(0, false);
mRotationCall.put(90, false);
mRotationCall.put(180, true);
mRotationCall.put(270, false);
break;
case 270:
mRotationCall.put(0, false);
mRotationCall.put(90, false);
mRotationCall.put(180, false);
mRotationCall.put(270, true);
break;
default:
break;
}
return flag;
}
5)根据不同的旋转角度,调整编码参数、发送SEI消息、旋转远端用户的画面、activity横竖屏
代码语言:javascript复制/**
* 1)推到远端流的编码参数
* 1.1)如果是竖屏,就设置竖屏的编码参数
* 1.2)如果是横屏,就设置横屏的编码参数
* 2)发送 SEI 消息
* 1.1)如果当前是横屏推流,就告诉房间内其他人,我当前在推横屏
* 1.1.1)房间内其他用户收到后,调整对应用户的 view 的宽高比,避免出现黑边
* 1.2)如果当前是竖屏推流,就告诉房间内其他人,我当前在推竖屏
* 1.1.1)房间内其他用户收到后,调整对应用户的 view 的宽高比,避免出现黑边
* 3)旋转远端用户的画面
* 3.1)我当前拉取到了其他人的流,我当前切换成 横屏/竖屏 ,需要旋转远端用户的画面,避免方向不一致
* 4)设置当前 activity 横屏 或 竖屏
* @param mOrientation 旋转的角度,如:0 90 180 270
*/
public void adjustVideoEnc(int mOrientation) {
// 本地画面旋转角度设置 0
TRTCCloudDef.TRTCRenderParams params = new TRTCCloudDef.TRTCRenderParams();
params.rotation = TRTC_VIDEO_ROTATION_0;
mTRTCCloud.setLocalRenderParams(params);
switch (mOrientation) {
case 0:
Log.d(TAG, "旋转角度 0");
// 1)设置推到远端的编码参数
TRTCCloudDef.TRTCVideoEncParam param = new TRTCCloudDef.TRTCVideoEncParam();
param.videoResolutionMode = TRTC_VIDEO_RESOLUTION_MODE_PORTRAIT; // 竖屏
mTRTCCloud.setVideoEncoderParam(param);
// 2)发送消息给房间内其它用户,说自己调整了横竖屏
JSONObject buildInfoJson = new JSONObject();
try {
buildInfoJson.put("TRTC_VIDEO_RESOLUTION_MODE", "PORTRAIT");
} catch (Exception e) {
}
mTRTCCloud.sendSEIMsg(buildInfoJson.toString().getBytes(), 1);
// 3)旋转订阅的远端画面
for (String uid : mRemoteUidList){
TRTCCloudDef.TRTCRenderParams renderParams = new TRTCCloudDef.TRTCRenderParams();
renderParams.rotation = TRTCCloudDef.TRTC_VIDEO_ROTATION_0;
renderParams.fillMode = TRTCCloudDef.TRTC_VIDEO_RENDER_MODE_FIT; // 默认值fill,这里手动指定fit
mTRTCCloud.setRemoteRenderParams(uid, TRTC_VIDEO_STREAM_TYPE_BIG, renderParams);
}
// 4)设置 activity 竖屏
VideoCallingActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
break;
case 90:
Log.d(TAG, "旋转角度 90");
// 1)设置推到远端的编码参数
param = new TRTCCloudDef.TRTCVideoEncParam();
param.videoResolutionMode = TRTC_VIDEO_RESOLUTION_MODE_LANDSCAPE; // 横屏
mTRTCCloud.setVideoEncoderParam(param);
// 2)发送消息给房间内其它用户,说自己调整了横竖屏
buildInfoJson = new JSONObject();
try {
buildInfoJson.put("TRTC_VIDEO_RESOLUTION_MODE", "LANDSCAPE");
} catch (Exception e) {
}
mTRTCCloud.sendSEIMsg(buildInfoJson.toString().getBytes(), 1);
// 3)旋转订阅的远端画面
for (String uid : mRemoteUidList){
TRTCCloudDef.TRTCRenderParams renderParams = new TRTCCloudDef.TRTCRenderParams();
renderParams.rotation = TRTCCloudDef.TRTC_VIDEO_ROTATION_180;
renderParams.fillMode = TRTCCloudDef.TRTC_VIDEO_RENDER_MODE_FIT; // 默认值fill,这里手动指定fit
mTRTCCloud.setRemoteRenderParams(uid, TRTC_VIDEO_STREAM_TYPE_BIG, renderParams);
}
// 4)设置 activity 横屏
VideoCallingActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
break;
case 180:
Log.d(TAG, "旋转角度 180");
// 1)设置推到远端的编码参数
param = new TRTCCloudDef.TRTCVideoEncParam();
param.videoResolutionMode = TRTC_VIDEO_RESOLUTION_MODE_PORTRAIT; // 竖屏
mTRTCCloud.setVideoEncoderParam(param);
// 2)发送消息给房间内其它用户,说自己调整了横竖屏
buildInfoJson = new JSONObject();
try {
buildInfoJson.put("TRTC_VIDEO_RESOLUTION_MODE", "PORTRAIT");
} catch (Exception e) {
}
mTRTCCloud.sendSEIMsg(buildInfoJson.toString().getBytes(), 1);
// 3)旋转订阅的远端画面
for (String uid : mRemoteUidList){
TRTCCloudDef.TRTCRenderParams renderParams = new TRTCCloudDef.TRTCRenderParams();
renderParams.rotation = TRTCCloudDef.TRTC_VIDEO_ROTATION_180;
renderParams.fillMode = TRTCCloudDef.TRTC_VIDEO_RENDER_MODE_FIT; // 默认值fill,这里手动指定fit
mTRTCCloud.setRemoteRenderParams(uid, TRTC_VIDEO_STREAM_TYPE_BIG, renderParams);
}
// 4)设置 activity 竖屏
VideoCallingActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
break;
case 270:
Log.d(TAG, "旋转角度 270");
// 1)设置推到远端的编码参数
param = new TRTCCloudDef.TRTCVideoEncParam();
param.videoResolutionMode = TRTC_VIDEO_RESOLUTION_MODE_LANDSCAPE; // 横屏
mTRTCCloud.setVideoEncoderParam(param);
// 2)发送消息给房间内其它用户,说自己调整了横竖屏
buildInfoJson = new JSONObject();
try {
buildInfoJson.put("TRTC_VIDEO_RESOLUTION_MODE", "LANDSCAPE");
} catch (Exception e) {
}
mTRTCCloud.sendSEIMsg(buildInfoJson.toString().getBytes(), 1);
// 3)旋转订阅的远端画面
for (String uid : mRemoteUidList){
TRTCCloudDef.TRTCRenderParams renderParams = new TRTCCloudDef.TRTCRenderParams();
renderParams.rotation = TRTCCloudDef.TRTC_VIDEO_ROTATION_0;
renderParams.fillMode = TRTCCloudDef.TRTC_VIDEO_RENDER_MODE_FIT; // 默认值fill,这里手动指定fit
mTRTCCloud.setRemoteRenderParams(uid, TRTC_VIDEO_STREAM_TYPE_BIG, renderParams);
}
// 4)设置 activity 横屏
VideoCallingActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
break;
default:
break;
}
}
6)收到SEI消息、首帧视频
代码语言:javascript复制/***
* 收到元旦用户告知,远端用户切换了横屏/竖屏,赶紧调整下 view 的窗口大小
* @param userId 发送消息的用户
* @param data 发送的数据
*/
@Override
public void onRecvSEIMsg(String userId, byte[] data) {
// 解析数据
Map<String, Object> objectMap = new Gson().fromJson(new String(data), Map.class);
Object mode = objectMap.get("TRTC_VIDEO_RESOLUTION_MODE");
if (mode instanceof String) {
// 横屏
if (mode.equals("LANDSCAPE")) {
// 当前收到远端用户告知为横屏,调整该远端用户 view 的大小,以免出现黑边
// 设置传入的宽高,仅表示 宽 > 高
adjustRemoteViewResolutionMode(userId, 1, 0);
// 竖屏
} else if (mode.equals("PORTRAIT")) {
// 当前收到远端用户告知为竖屏,调整该远端用户 view 的大小,以免出现黑边
// 设置传入的宽高,仅表示 宽 < 高
adjustRemoteViewResolutionMode(userId, 0, 1);
}
}
}
/***
* 收到用户的第一帧视频画面
* 如果 userId 为空值,代表 SDK 已经开始渲染自己本地的视频画面
* 如果 userId 不为空,代表 SDK 已经开始渲染远端用户的视频画面
* @param userId 哪个用户的视频首帧
* @param streamType 流类型
* @param width 宽
* @param height 高
*/
@Override
public void onFirstVideoFrame(String userId, int streamType, int width, int height) {
/**
* 不为空,即为远端用户的,也仅仅调整远端用户的 view
*/
if (userId != null) {
// 根据首帧收到的宽高,来确定流的分辨率,以便调整 view 的宽高
// 宽 > 高,说明要 view的 宽 > 高
// 宽 < 高,说明要 view的 宽 < 高
adjustRemoteViewResolutionMode(userId, width, height);
}
}
代码语言:javascript复制/**
* 调整远端 view 的大小
* 当传入的 宽 > 高,说明是要横着展示远端的流,那么 view 的 宽 > 高 来设置
* 1)如果 view 的 宽 > 高 , 就不做处理
* 2)如果 view 的 宽 < 高 , 那就把 宽和高 对调下大小
* 当传入的 宽 < 高,说明是要横着展示远端的流,那么 view 的 宽 < 高 来设置
* 1)如果 view 的 宽 < 高 , 就不做处理
* 2)如果 view 的 宽 > 高 , 那就把 宽和高 对调下大小
* @param userId 对应的远端用户
* @param width 宽度
* @param height 高度
*/
private void adjustRemoteViewResolutionMode(String userId, int width, int height) {
// 获取对应 userId 的下标
int index = mRemoteUidList.indexOf(userId);
// index 不等于 -1,说明存在该 userId
if (index != -1) {
// 根据 index 获取对应用户的 view
TXCloudVideoView videoView = mRemoteViewList.get(index);
// 获取该 userId 对应 view 的 LayoutParams
ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams();
// 如果传入过来要求的 宽 > 高,即 横着
if (width > height) {
// 如果已经给它的 宽 < 高,那么就要调整他,使它的 宽 > 高,宽高对调即可
if (layoutParams.width < layoutParams.height) {
int tempWidth = layoutParams.width;
layoutParams.width = layoutParams.height;
layoutParams.height = tempWidth;
videoView.setLayoutParams(layoutParams);
}
// 如果传入过来要求的 宽 < 高,即 竖着
} else if (width < height) {
// 如果已经给它的 宽 > 高,那么就要调整他,使它的 宽 < 高,宽高对调即可
if (layoutParams.width > layoutParams.height) {
int tempWidth = layoutParams.width;
layoutParams.width = layoutParams.height;
layoutParams.height = tempWidth;
videoView.setLayoutParams(layoutParams);
}
}
}
}
代码示例
当运行demo时,记得修改 GenerateTestUserSig 文件中的 SDKAPPID、SECRETKEY,才能正常运行起来。
文件:DebugsrcmainjavacomtencenttrtcdebugGenerateTestUserSig.java
演示示例代码:如附件
参考文档
视频画面旋转和缩放:https://cloud.tencent.com/document/product/647/32237