Java对接腾讯多人音视频房间示例

最近在对接腾讯的多人音视频房间,做一个类似于腾讯会议的工具,至于为什么不直接用腾讯会议,这个我也不知道,当然我也不敢问

首先是腾讯官方的文档地址: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 鉴权票据
     * <p>
     * 【参数说明】
     *
     * @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 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。
     * <p>
     * 【参数说明】
     *
     * @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 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。
     * <p>
     * 【参数说明】
     *
     * @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对接腾讯多人音视频房间回调接口示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/256961.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Spring】11 EnvironmentAware 接口

文章目录 1. 简介2. 作用3. 使用3.1 创建并实现接口3.2 配置 Bean 信息3.3 创建启动类3.4 启动 4. 应用场景总结 Spring 框架为开发者提供了丰富的扩展点&#xff0c;其中之一就是 Bean 生命周期中的回调接口。本文将着重介绍一个与环境&#xff08;Environment&#xff09;相关…

第三讲GNSS相关时间系统和转换 第四讲观测值的产生和分类 | GNSS(RTK)课程学习笔记day2

说明&#xff1a;以下笔记来自计算机视觉life吴桐老师课程&#xff1a;从零掌握GNSS、RTK定位[链接]&#xff0c;从零掌握RTKLIB[链接]。非原创&#xff01;且笔记仅供自身与大家学习使用&#xff0c;无利益目的。 第三讲 GNSS相关时间系统和转换 GPS卫星的位置在时间过程中是…

【数据结构】(堆)Top-k|堆排序

目录 概念&#xff1a; 堆的实现 构建 初始化 销毁 插入元素 往上调整 删除堆顶元素 往下调整 返回堆顶元素 返回有效个数 是否为空 堆排序 Top-k问题 ​编辑 创建数据 堆top-k 概念&#xff1a; 堆是将数据按照完全二叉树存储方式存储到一维数组中&#xff…

python的argparse在celery中调用parser.parse_args()参数解析报错解决

文章目录 一、前言二、报错提示三、解决方案四、总结 一、前言 调用flask中的api接口&#xff0c;会调用我的异步函数&#xff0c;而异步函数是在celery框架中执行 下图的执行流程也没有问题 经过调试发现问题出在**parser.parse_args()**函数这里 二、报错提示 命令行运行…

在Pytorch中自定义dataset读取数据

这里使用的是经典的花分类数据集 下载地址&#xff1a;https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz 下载结束后进行解压&#xff0c;可以得到五种不同种类花的图片&#xff0c;如上图所示 主函数 main def main():device tor…

zookeeper:启动后占用8080端口问题解决

ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务。它为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维护、域名服务、分布式同步、组服务等。 我们经常在运行zookeeper服务时&#xff0c;不需要配置服务端口&#xff0c;…

二叉树的最大深度(LeetCode 104)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路方法一&#xff1a;深度优先搜索GolangC 方法二&#xff1a;广度优先搜索GolangC 参考文献 1.问题描述 给定一个二叉树 root &#xff0c;返回其最大深度。 叉树的「最大深度」是指从根节点到最远叶子节点的最长路径上的节…

Apollo开放平台9.0让自动驾驶开发者轻松上手

文章目录 平台架构&#xff1a;基础环境&#xff1a;开始使用&#xff1a;体验心得: 在自动驾驶技术飞速发展的今天&#xff0c;成为这个领域的一名开发者是一次挑战、一次冒险&#xff0c;更是一次心灵之旅。作为这个领域的先锋之一&#xff0c;Apollo开放平台9.0于12月19日发…

TSINGSEE青犀边缘AI计算基于车辆结构化数据的车辆监控方案

随着人工智能技术的不断发展&#xff0c;边缘AI技术逐渐成为智能交通领域的研究热点。其中&#xff0c;基于边缘AI的车辆结构化数据技术与车辆监控系统是实现智能交通系统的重要手段之一。为了满足市场需求&#xff0c;TSINGSEE青犀边缘AI智能分析网关/视频智能分析平台推出了一…

【百度PARL】强化学习笔记

文章目录 强化学习基本知识一些框架Value-based的方法Q表格举个例子 强化的概念TD更新 Sarsa算法SampleSarsa Agent类 On_policy vs off_policy函数逼近与神经网络DQN算法DQN创新点DQN代码实现model.pyalgorithm.pyagent.py总结&#xff1a;举个例子 实战 视频&#xff1a;世界…

【SQL】根据年月,查询月份中每一天的数据量

传入YYYY-MM-01&#xff0c;查询这个月中每一天的数据量&#xff0c;没有数据的天数用0表示 WITH RECURSIVE DateRange AS (SELECT :startDate AS DateUNION ALLSELECT DATE_ADD(Date, INTERVAL 1 DAY) FROM DateRangeWHERE Date < LAST_DAY(:startDate) ) SELECTdr.Date,CO…

docker中如何使用Arthas

docker中如何使用Arthas 一、操作步骤1、首先拷贝arthas包下来&#xff1a;2、其次选中你需要查看的容器ID&#xff1a;3、拷贝arthas程序包到容器目录下&#xff1a;4、进入到容器目录5、进入到第3步映射到容器的路径&#xff0c;并使用ll查看是否存在 arthas-boot.jar6、使用…

全球移动通信(2G/3G/4G/5G)频谱分布情况

一、概述 随着通信技术的不断发展&#xff0c;全球各国都在积极推进2G、3G、4G、5G网络的建设和应用。根据FCC统计&#xff0c;目前全球移动通信频谱分布如下&#xff1a; 二、分布 &#xff08;一&#xff09;俄罗斯 2G&#xff1a;主要使用900MHz和1800MHz两个频段。其中&…

Postman接口测试之Postman常用的快捷键

作为一名IT程序猿&#xff0c;不懂一些工具的快捷方式&#xff0c;应该会被鄙视的吧。收集了一些Postman的快捷方式&#xff0c;大家一起动手操作~ 简单操作 xc 请求 操作MAC系统windows系统请求网址 ⌘L Ctrl L 保存请求 ⌘S Ctrl S 保存请求为 ⇧⌘S Ctrl Shift S发送…

云原生之深入解析Kubernetes集群发生网络异常时如何排查

一、Pod 网络异常 网络不可达&#xff0c;主要现象为 ping 不通&#xff0c;其可能原因为&#xff1a; 源端和目的端防火墙&#xff08;iptables, selinux&#xff09;限制&#xff1b; 网络路由配置不正确&#xff1b; 源端和目的端的系统负载过高&#xff0c;网络连接数满…

如何搭建一个买衣服的微信小程序商城

随着移动互联网的普及&#xff0c;微信小程序商城已经成为众多商家开展线上业务的重要平台。本文将介绍如何搭建一个卖衣服的微信小程序商城&#xff0c;帮助您实现线上业务的拓展。 第一步&#xff1a;登录乔拓云平台进入商城后台管理页面 在浏览器中搜索乔拓云平台并登录&a…

广汽本田售后服务技术技能竞赛总决赛

从2001年开始&#xff0c;广汽本田售后服务技术技能大赛年年举办&#xff0c;层层筛选、择优选拔&#xff0c;以赛促练全面提升服务领域一线人员的业务能力。 广汽本田售后服务技术技能大赛在赛程设置、考核内容和形式等方面进行了全面升级与强化&#xff0c;创新突破服务精英的…

科士达新能源荣获CTC国检集团“产品碳足迹证书”

2023年12月14-15日&#xff0c;“2023光伏行业年度大会”在江苏省宿迁市召开&#xff0c;行业主管部门、行业组织、知名专家和光伏企业等代表莅临现场。科士达新能源受邀出席&#xff0c;并在同期举办的”光伏产品碳足迹与碳中和研讨会”上&#xff0c;荣获CTC国检集团“产品碳…

WebMvcConfigurer接口详解及使用方式(Spring-WebMvc)

简介 如下图所示WebMvcConfigurer是spring-webmvc jar包下的一个接口&#xff0c;spring-webmvc jar包又来源于spring-boot-starter-web&#xff0c;所以要使用WebMvcConfigurer要引入spring-boot-starter-web依赖。WebMvcConfigurer接口提供了常用的web应用拦截方法。通过实现…

Elasticsearch 索引生命周期和翻滚 (rollover) 策略

Elasticsearch 是搜索引擎中的摇滚明星&#xff0c;它的蓬勃发展在于使你的数据井井有条且速度快如闪电。 但当你的数据成为一场摇滚音乐会时&#xff0c;管理其生命周期就变得至关重要。 正确使用索引生命周期管理 (ILM) 和 rollover 策略&#xff0c;你的后台工作人员可确保顺…