最近在对接腾讯的多人音视频房间,做一个类似于腾讯会议的工具,至于为什么不直接用腾讯会议,这个我也不知道,当然我也不敢问
首先是腾讯官方的文档地址:https://cloud.tencent.com/document/product/1690
我是后端所以只负责对接服务端api即可
从它这个简介可以看出这个REST API最主要的还是拼接请求的这个url
https://xxxxxx/$service/$version/$kind/$command?sdkappid=$SDKAppID&identifier=$identifier&usersig=$usersig&random=99999999&contenttype=json
这里面有三个参数是需要我们去开通账号获取的(sdkappid identifier usersig )
大家先点上面右上角那个控制台登录进去,没有账号的自己开通账号,可以先用体验版的测试,就是下面这个页面 项目管理
里面没有项目的话自己创建一个,自己搞不定的直接找他们客服,顾客是上帝,他们会热心给你解答的!!!
然后再点操作里面的项目配置
这里面有两个很重要的参数SDKAppId和Secret Key,先记录下来
注意测试版的只能用七天,七天到期了你可以再创建一个项目继续测试,没必要一开始就花钱
我们还需要拿到admin用户的用户名,这个用户名在另外一个平台才看的到,不过账号是一样的,你登录一个地方就可以直接访问 即时通信 IM 控制台
里面一开始就有一个默认的administrator,我们identifier直接用这个就可以
这里有个地方要注意,后端调用api的usersig是必须要用admin的用户名才可以的,也就是后端生成usersig固定写死用administrator。但是他们前端用户在进入房间的时候应该是用当前用户的userId生成签名就可以,不然后面回调的时候你就不知道谁进入了房间的
必要的参数拿到了我们就可以开始对接接口了,首先是对接的RoomctlUtil
import com.alibaba.fastjson.JSONObject;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 多人视频工具类
*
* @author Sakura
* @date 2023/12/14 18:19
*/
@Component
@Log
public class RoomctlUtil {
@Value("${tencent.roomctl.SDKAppId}")
private Long SDKAppId;
@Value("${tencent.roomctl.SecretKey}")
private String SecretKey;
// 这是固定用 administrator即可
@Value("${tencent.roomctl.identifier}")
private String identifier;
// SDKAppID 所在国家/地区对应的专属域名
private static final String BASE_URL = "https://roomkit.trtc.tencent-cloud.com/room_api/v1";
// 创建房间api
private static final String ROOMCTL_CREATE_API = "/roomctl/create";
// 修改房间信息api
private static final String ROOMCTL_UPDATE_API = "/roomctl/update/room_info";
// 销毁房间api
private static final String ROOMCTL_DESTROY_API = "/roomctl/destroy";
// 获取房间管理员api
private static final String ROOMCTL_GET_ADMIN_LIST_API = "/roomctl/get/admin_list";
// 获取房间信息api
private static final String ROOMCTL_GET_ROOM_INFO_API = "/roomctl/get/room_info";
// 配置回调地址api
private static final String ROOMCTL_CALLBACK_SET_API = "/callback/set";
// 修改回调地址api
private static final String ROOMCTL_CALLBACK_UPDATE_API = "/callback/update";
@Autowired
TxUrlUtil txUrlUtil;
public String createRoomctl(String ownerId, String roomId, Integer maxSeatCount, String roomName,
Integer roomType, Integer speechMode, Boolean disableVideo,
Boolean disableAudio, Boolean disableMessage, Long scheduleStartTime,
Long scheduleEndTime) {
// 封装参数
JSONObject json = new JSONObject();
json.put("ownerId", ownerId); // 房主 ID
json.put("roomId", roomId); // 房间 ID
JSONObject roomJson = new JSONObject();
if (maxSeatCount != null) {
roomJson.put("maxSeatCount", maxSeatCount); // 最大麦位数量,默认为套餐包上限,(直播最大16个 默认16)
}
if (!StringUtils.isEmpty(roomName)) {
roomJson.put("roomName", roomName); // 房间名称。默认为房间 ID
}
if (roomType != null) {
roomJson.put("roomType", roomType); // 房间类型:1为会议,2为直播 默认为:会议1
}
if (speechMode != null) {
roomJson.put("speechMode", speechMode); // 麦控模式:1为自由发言模式,2为举手发言模式,3为就座后发言(抢麦位) 默认为:自由发言模式1
}
if (disableVideo != null) {
roomJson.put("disableVideo", disableVideo); // 全体禁画 默认 false
}
if (disableAudio != null) {
roomJson.put("disableAudio", disableAudio); // 全体静音 默认 false
}
if (disableMessage != null) {
roomJson.put("disableMessage", disableMessage); // 禁止所有成员发送文本消息 默认 false
}
if (scheduleStartTime != null) {
json.put("scheduleStartTime", scheduleStartTime); // 预定会议开始时间戳。如果没有设置会议开始时间,则默认为普通房间。(仅会议场景支持)
}
if (scheduleEndTime != null) {
json.put("scheduleEndTime", scheduleEndTime); // 预定会议结束时间戳。如果没有设置会议结束时间,则默认为45分钟,超时房间30s内无人房间会被自动解散。
}
json.put("roomInfo", roomJson);
String url = txUrlUtil.getTxUrl(BASE_URL, ROOMCTL_CREATE_API, identifier, SDKAppId);
log.info("创建多人视频房间URL:" + url);
log.info("创建多人视频房间参数:" + json);
String resultStr = "ok";
try {
String result = HttpsRequestUtil.sendPostRequest(url, json.toString());
log.info("创建多人视频房间返回:" + result);
JSONObject resultJson = JSONObject.parseObject(result);
if (!"Success".equals(resultJson.getString("errorMessage"))) {
resultStr = resultJson.getString("errorMessage");
}
} catch (Exception e) {
log.info("创建多人视频房间出现异常");
e.printStackTrace();
return "接口异常,请联系管理员";
}
return resultStr;
}
public String updateRoomctl(String roomId, Integer maxSeatCount, String roomName,
Integer speechMode, Boolean disableVideo,
Boolean disableAudio, Boolean disableMessage, Long scheduleStartTime,
Long scheduleEndTime) {
// 封装参数
JSONObject json = new JSONObject();
json.put("roomId", roomId); // 房间 ID
if (maxSeatCount != null) {
json.put("maxSeatCount", maxSeatCount); // 最大麦位数量,默认为套餐包上限,(直播最大16个 默认16)
}
if (!StringUtils.isEmpty(roomName)) {
json.put("roomName", roomName); // 房间名称。默认为房间 ID
}
if (speechMode != null) {
json.put("speechMode", speechMode); // 麦控模式:1为自由发言模式,2为举手发言模式,3为就座后发言(抢麦位) 默认为:自由发言模式1
}
if (disableVideo != null) {
json.put("disableVideo", disableVideo); // 全体禁画 默认 false
}
if (disableAudio != null) {
json.put("disableAudio", disableAudio); // 全体静音 默认 false
}
if (disableMessage != null) {
json.put("disableMessage", disableMessage); // 禁止所有成员发送文本消息 默认 false
}
if (scheduleStartTime != null) {
json.put("scheduleStartTime", scheduleStartTime); // 预定会议开始时间戳。如果没有设置会议开始时间,则默认为普通房间。(仅会议场景支持)
}
if (scheduleEndTime != null) {
json.put("scheduleEndTime", scheduleEndTime); // 预定会议结束时间戳。如果没有设置会议结束时间,则默认为45分钟,超时房间30s内无人房间会被自动解散。
}
String url = txUrlUtil.getTxUrl(BASE_URL, ROOMCTL_UPDATE_API, identifier, SDKAppId);
log.info("修改多人视频房间URL:" + url);
log.info("修改多人视频房间参数:" + json);
String resultStr = "ok";
try {
String result = HttpsRequestUtil.sendPostRequest(url, json.toString());
log.info("修改多人视频房间返回:" + result);
JSONObject resultJson = JSONObject.parseObject(result);
if (!"Success".equals(resultJson.getString("errorMessage"))) {
resultStr = resultJson.getString("errorMessage");
}
} catch (Exception e) {
log.info("修改多人视频房间出现异常");
e.printStackTrace();
return "接口异常,请联系管理员";
}
return resultStr;
}
public String deleteRoomctl(String roomId) {
// 封装参数
JSONObject json = new JSONObject();
json.put("roomId", roomId); // 房间 ID
String url = txUrlUtil.getTxUrl(BASE_URL, ROOMCTL_DESTROY_API, identifier, SDKAppId);
log.info("修改多人视频房间URL:" + url);
log.info("修改多人视频房间参数:" + json);
String resultStr = "ok";
try {
String result = HttpsRequestUtil.sendPostRequest(url, json.toString());
log.info("修改多人视频房间返回:" + result);
JSONObject resultJson = JSONObject.parseObject(result);
if (!"Success".equals(resultJson.getString("errorMessage"))) {
resultStr = resultJson.getString("errorMessage");
}
} catch (Exception e) {
log.info("修改多人视频房间出现异常");
e.printStackTrace();
return "接口异常,请联系管理员";
}
return resultStr;
}
public String getTxRoomctlIfo(String roomId) {
// 封装参数
JSONObject json = new JSONObject();
json.put("roomId", roomId); // 房间 ID
String url = txUrlUtil.getTxUrl(BASE_URL, ROOMCTL_GET_ROOM_INFO_API, identifier, SDKAppId);
log.info("获取多人视频房间信息URL:" + url);
log.info("获取多人视频房间信息参数:" + json);
String resultStr = "ok";
try {
String result = HttpsRequestUtil.sendPostRequest(url, json.toString());
log.info("获取多人视频房间信息返回:" + result);
JSONObject resultJson = JSONObject.parseObject(result);
if (!"Success".equals(resultJson.getString("errorMessage"))) {
resultStr = resultJson.getString("errorMessage");
}
} catch (Exception e) {
log.info("获取多人视频房间信息出现异常");
e.printStackTrace();
return "接口异常,请联系管理员";
}
return resultStr;
}
public String getTxRoomctlAdmins(String roomId) {
// 封装参数
JSONObject json = new JSONObject();
json.put("roomId", roomId); // 房间 ID
String url = txUrlUtil.getTxUrl(BASE_URL, ROOMCTL_GET_ADMIN_LIST_API, identifier, SDKAppId);
log.info("获取多人视频房间管理员URL:" + url);
log.info("获取多人视频房间管理员参数:" + json);
String resultStr = "ok";
try {
String result = HttpsRequestUtil.sendPostRequest(url, json.toString());
log.info("获取多人视频房间管理员返回:" + result);
JSONObject resultJson = JSONObject.parseObject(result);
if (!"Success".equals(resultJson.getString("errorMessage"))) {
resultStr = resultJson.getString("errorMessage");
}
} catch (Exception e) {
log.info("获取多人视频房间管理员出现异常");
e.printStackTrace();
return "接口异常,请联系管理员";
}
return resultStr;
}
public String createCallback(String address, List<String> actions) {
// 封装参数
JSONObject json = new JSONObject();
json.put("address", address); // 房间 ID
json.put("actions", actions); // 房间 ID
String url = txUrlUtil.getTxUrl(BASE_URL, ROOMCTL_CALLBACK_SET_API, identifier, SDKAppId);
log.info("配置回调地址URL:" + url);
log.info("配置回调地址参数:" + json);
String resultStr = "ok";
try {
String result = HttpsRequestUtil.sendPostRequest(url, json.toString());
log.info("配置回调地址返回:" + result);
JSONObject resultJson = JSONObject.parseObject(result);
if (!"Success".equals(resultJson.getString("errorMessage"))) {
resultStr = resultJson.getString("errorMessage");
}
} catch (Exception e) {
log.info("配置回调地址出现异常");
e.printStackTrace();
return "接口异常,请联系管理员";
}
return resultStr;
}
public String updateCallback(String address, List<String> actions) {
// 封装参数
JSONObject json = new JSONObject();
json.put("address", address); // 房间 ID
json.put("actions", actions); // 房间 ID
String url = txUrlUtil.getTxUrl(BASE_URL, ROOMCTL_CALLBACK_UPDATE_API, identifier, SDKAppId);
log.info("修改回调地址URL:" + url);
log.info("修改回调地址参数:" + json);
String resultStr = "ok";
try {
String result = HttpsRequestUtil.sendPostRequest(url, json.toString());
log.info("修改回调地址返回:" + result);
JSONObject resultJson = JSONObject.parseObject(result);
if (!"Success".equals(resultJson.getString("errorMessage"))) {
resultStr = resultJson.getString("errorMessage");
}
} catch (Exception e) {
log.info("修改回调地址出现异常");
e.printStackTrace();
return "接口异常,请联系管理员";
}
return resultStr;
}
}
里面用到的工具类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Sakura
* @date 2023/12/14 20:22
*/
@Component
public class TxUrlUtil {
@Autowired
UserSigUtil userSigUtil;
public String getTxUrl(String baseUrl, String api, String identifier, Long SDKAppId) {
StringBuffer urlSB = new StringBuffer();
urlSB.append(baseUrl);
urlSB.append(api);
// 创建应用时即时通信 IM 控制台分配的 SDKAppID
urlSB.append("?sdkappid=");
urlSB.append(SDKAppId);
// 必须为 App 管理员账号
urlSB.append("&identifier=");
urlSB.append(identifier);
// App 管理员账号生成的签名,具体操作请参见 生成 UserSig
urlSB.append("&usersig=");
String userSig = userSigUtil.getUserSig(identifier,86400l);
urlSB.append(userSig);
// 请输入随机的32位无符号整数,取值范围0 - 4294967295
urlSB.append("&random=");
urlSB.append(RandomNumberGenerator.generateRandomUnsignedInt());
// 请求格式固定值为json
urlSB.append("&contenttype=json");
return urlSB.toString();
}
}
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author Sakura
* @date 2023/12/15 16:29
*/
@Component
@Log
public class UserSigUtil {
@Value("${tencent.roomctl.SDKAppId}")
private Long SDKAppId;
@Value("${tencent.roomctl.SecretKey}")
private String SecretKey;
@Value("${tencent.roomctl.identifier}")
private String identifier;
public String getUserSig(String identifier, Long expire) {
TLSSigAPIv2 tlsSigAPIv2 = new TLSSigAPIv2(SDKAppId, SecretKey);
String userSig = tlsSigAPIv2.genUserSig(identifier, expire);
return userSig;
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author Sakura
* @date 2023/12/14 19:07
*/
public class HttpsRequestUtil {
public static String sendPostRequest(String urlString, String jsonInputString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法为POST
connection.setRequestMethod("POST");
// 设置请求头
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
// 启用输出流,用于发送请求数据
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input, 0, input.length);
}
// 获取响应
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
return response.toString();
} finally {
// 关闭连接
connection.disconnect();
}
}
}
下面这两个加密的直接从腾讯文档复制过来的
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.security.*;
import java.util.Arrays;
import java.util.zip.Deflater;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.json.JSONObject;
public class TLSSigAPIv2 {
final private long sdkappid;
final private String key;
public TLSSigAPIv2(long sdkappid, String key) {
this.sdkappid = sdkappid;
this.key = key;
}
/**
* 【功能说明】用于签发 TRTC 和 IM 服务中必须要使用的 UserSig 鉴权票据
*
* 【参数说明】
*
* @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
* @param expire - UserSig 票据的过期时间,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。
* @return usersig -生成的签名
*/
public String genUserSig(String userid, long expire) {
return genUserSig(userid, expire, null);
}
/**
* 【功能说明】
* 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
* PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
* - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
* - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
* 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。
*
* 【参数说明】
*
* @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
* @param expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
* @param roomid - 房间号,用于指定该 userid 可以进入的房间号
* @param privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
* - 第 1 位:0000 0001 = 1,创建房间的权限
* - 第 2 位:0000 0010 = 2,加入房间的权限
* - 第 3 位:0000 0100 = 4,发送语音的权限
* - 第 4 位:0000 1000 = 8,接收语音的权限
* - 第 5 位:0001 0000 = 16,发送视频的权限
* - 第 6 位:0010 0000 = 32,接收视频的权限
* - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
* - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限
* - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
* - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
* @return usersig - 生成带userbuf的签名
*/
public String genPrivateMapKey(String userid, long expire, long roomid, long privilegeMap) {
byte[] userbuf = genUserBuf(userid, roomid, expire, privilegeMap, 0, ""); //生成userbuf
return genUserSig(userid, expire, userbuf);
}
/**
* 【功能说明】
* 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
* PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
* - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
* - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
* 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。
*
* 【参数说明】
*
* @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
* @param expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
* @param roomstr - 字符串房间号,用于指定该 userid 可以进入的房间号
* @param privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
* - 第 1 位:0000 0001 = 1,创建房间的权限
* - 第 2 位:0000 0010 = 2,加入房间的权限
* - 第 3 位:0000 0100 = 4,发送语音的权限
* - 第 4 位:0000 1000 = 8,接收语音的权限
* - 第 5 位:0001 0000 = 16,发送视频的权限
* - 第 6 位:0010 0000 = 32,接收视频的权限
* - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
* - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限
* - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
* - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
* @return usersig - 生成带userbuf的签名
*/
public String genPrivateMapKeyWithStringRoomID(String userid, long expire, String roomstr, long privilegeMap) {
byte[] userbuf = genUserBuf(userid, 0, expire, privilegeMap, 0, roomstr); //生成userbuf
return genUserSig(userid, expire, userbuf);
}
private String hmacsha256(String identifier, long currTime, long expire, String base64Userbuf) {
String contentToBeSigned = "TLS.identifier:" + identifier + "\n"
+ "TLS.sdkappid:" + sdkappid + "\n"
+ "TLS.time:" + currTime + "\n"
+ "TLS.expire:" + expire + "\n";
if (null != base64Userbuf) {
contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
}
try {
byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");
hmac.init(keySpec);
byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes(StandardCharsets.UTF_8));
return (Base64.getEncoder().encodeToString(byteSig)).replaceAll("\\s*", "");
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
return "";
}
}
private String genUserSig(String userid, long expire, byte[] userbuf) {
long currTime = System.currentTimeMillis() / 1000;
JSONObject sigDoc = new JSONObject();
sigDoc.put("TLS.ver", "2.0");
sigDoc.put("TLS.identifier", userid);
sigDoc.put("TLS.sdkappid", sdkappid);
sigDoc.put("TLS.expire", expire);
sigDoc.put("TLS.time", currTime);
String base64UserBuf = null;
if (null != userbuf) {
base64UserBuf = Base64.getEncoder().encodeToString(userbuf).replaceAll("\\s*", "");
sigDoc.put("TLS.userbuf", base64UserBuf);
}
String sig = hmacsha256(userid, currTime, expire, base64UserBuf);
if (sig.length() == 0) {
return "";
}
sigDoc.put("TLS.sig", sig);
Deflater compressor = new Deflater();
compressor.setInput(sigDoc.toString().getBytes(StandardCharsets.UTF_8));
compressor.finish();
byte[] compressedBytes = new byte[2048];
int compressedBytesLength = compressor.deflate(compressedBytes);
compressor.end();
return (new String(Base64URL.base64EncodeUrl(Arrays.copyOfRange(compressedBytes,
0, compressedBytesLength)))).replaceAll("\\s*", "");
}
public byte[] genUserBuf(String account, long dwAuthID, long dwExpTime,
long dwPrivilegeMap, long dwAccountType, String RoomStr) {
//视频校验位需要用到的字段,按照网络字节序放入buf中
/*
cVer unsigned char/1 版本号,填0
wAccountLen unsigned short /2 第三方自己的帐号长度
account wAccountLen 第三方自己的帐号字符
dwSdkAppid unsigned int/4 sdkappid
dwAuthID unsigned int/4 群组号码
dwExpTime unsigned int/4 过期时间 ,直接使用填入的值
dwPrivilegeMap unsigned int/4 权限位,主播0xff,观众0xab
dwAccountType unsigned int/4 第三方帐号类型
*/
int accountLength = account.length();
int roomStrLength = RoomStr.length();
int offset = 0;
int bufLength = 1 + 2 + accountLength + 20 ;
if (roomStrLength > 0) {
bufLength = bufLength + 2 + roomStrLength;
}
byte[] userbuf = new byte[bufLength];
//cVer
if (roomStrLength > 0) {
userbuf[offset++] = 1;
} else {
userbuf[offset++] = 0;
}
//wAccountLen
userbuf[offset++] = (byte) ((accountLength & 0xFF00) >> 8);
userbuf[offset++] = (byte) (accountLength & 0x00FF);
//account
for (; offset < 3 + accountLength; ++offset) {
userbuf[offset] = (byte) account.charAt(offset - 3);
}
//dwSdkAppid
userbuf[offset++] = (byte) ((sdkappid & 0xFF000000) >> 24);
userbuf[offset++] = (byte) ((sdkappid & 0x00FF0000) >> 16);
userbuf[offset++] = (byte) ((sdkappid & 0x0000FF00) >> 8);
userbuf[offset++] = (byte) (sdkappid & 0x000000FF);
//dwAuthId,房间号
//dwAuthId, room number
userbuf[offset++] = (byte) ((dwAuthID & 0xFF000000) >> 24);
userbuf[offset++] = (byte) ((dwAuthID & 0x00FF0000) >> 16);
userbuf[offset++] = (byte) ((dwAuthID & 0x0000FF00) >> 8);
userbuf[offset++] = (byte) (dwAuthID & 0x000000FF);
//expire,过期时间,当前时间 + 有效期(单位:秒)
//expire,Expiration time, current time + validity period (unit: seconds)
long currTime = System.currentTimeMillis() / 1000;
long expire = currTime + dwExpTime;
userbuf[offset++] = (byte) ((expire & 0xFF000000) >> 24);
userbuf[offset++] = (byte) ((expire & 0x00FF0000) >> 16);
userbuf[offset++] = (byte) ((expire & 0x0000FF00) >> 8);
userbuf[offset++] = (byte) (expire & 0x000000FF);
//dwPrivilegeMap,权限位
//dwPrivilegeMap,Permission bits
userbuf[offset++] = (byte) ((dwPrivilegeMap & 0xFF000000) >> 24);
userbuf[offset++] = (byte) ((dwPrivilegeMap & 0x00FF0000) >> 16);
userbuf[offset++] = (byte) ((dwPrivilegeMap & 0x0000FF00) >> 8);
userbuf[offset++] = (byte) (dwPrivilegeMap & 0x000000FF);
//dwAccountType,账户类型
//dwAccountType,account type
userbuf[offset++] = (byte) ((dwAccountType & 0xFF000000) >> 24);
userbuf[offset++] = (byte) ((dwAccountType & 0x00FF0000) >> 16);
userbuf[offset++] = (byte) ((dwAccountType & 0x0000FF00) >> 8);
userbuf[offset++] = (byte) (dwAccountType & 0x000000FF);
if (roomStrLength > 0) {
//roomStrLen
userbuf[offset++] = (byte) ((roomStrLength & 0xFF00) >> 8);
userbuf[offset++] = (byte) (roomStrLength & 0x00FF);
//roomStr
for (; offset < bufLength; ++offset) {
userbuf[offset] = (byte) RoomStr.charAt(offset - (bufLength - roomStrLength));
}
}
return userbuf;
}
}
import java.util.Base64;
public class Base64URL {
public static byte[] base64EncodeUrl(byte[] input) {
byte[] base64 = Base64.getEncoder().encode(input);
for (int i = 0; i < base64.length; ++i)
switch (base64[i]) {
case '+':
base64[i] = '*';
break;
case '/':
base64[i] = '-';
break;
case '=':
base64[i] = '_';
break;
default:
break;
}
return base64;
}
}
调用的就随便写一个示例吧
创建房间的时候其实只需要一个ownerId和roomId,ownerId建议用你自己的用户ID,roomId随机生成一个即可
/**
* @Description: 多人视频管理
* @Author: Sakura
* @Date: 2023/12/14 17:44
*/
@RestController
@RequestMapping("/txcloud/roomctl")
public class TxRoomctlController {
@Autowired
RoomctlUtil roomctlUtil;
/** 预约会议
* @Description:
* @Author: Sakura
* @Date: 2023/12/14 17:31
*/
@PostMapping("/scheduleMeeting")
public Result scheduleMeeting(@RequestBody ScheduleMeetingRequest scheduleMeetingRequest) {
// 调用腾讯创建房间接口
String str = roomctlUtil.createRoomctl("123456", "123456", null,
null, null, null, null, null, null, null, null);
if (!"ok".equals(str)) {
return Result.ERROR(str);
}
return Result.SUCCESS();
}
}
然后又回到前端调用userSig的问题,一般SecretKey都放在后端不会给前端的, 这里我们写一个方法给他们调就可以
@GetMapping("/getUserSig")
public Result getInfo() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("SDKAppId", SDKAppId);
jsonObject.put("identifier", LoginUserUtils.getUserId());
jsonObject.put("userSig", userSigUtil.getUserSig(LoginUserUtils.getUserId(), 86400l));
return Result.SUCCESS(jsonObject);
}
这样基础接口就对接完了,我们还需写一个回调监听用来接收回调信息,这样可以便于我们收集用户进入房间退出房间这些数据等
Java对接腾讯多人音视频房间回调接口示例