云端录制回调

本文针对 新版云端录制功能 的回调事件进行具体说明。

配置信息

Tencent RTC 控制台 支持自助配置回调信息,配置完成后即可接收事件回调通知。



注意:
您需要提前准备以下信息并在控制台完成 回调配置
必要项:接收回调通知的 HTTP/HTTPS 服务器地址。
可选项计算签名的密钥 key,由您自定义一个最大32个字符的 key,以大小写字母及数字组成。

超时重试

事件回调服务器在发送消息通知后,5秒内没有收到您的服务器的响应,即认为通知失败。首次通知失败后会立即重试,后续失败会以10秒的间隔继续重试,直到消息存续时间超过1分钟,不再重试。

回调接口

您可以提供一个接收回调的 HTTP/HTTPS 服务网关来订阅回调消息。当相关事件发生时,云录制系统会回调事件通知到您的消息接收服务器。

事件回调消息格式

事件回调消息以 HTTP/HTTPS POST 请求发送给您的服务器,其中:
字符编码格式:UTF-8。
请求:body 格式为 JSON。
应答:HTTP STATUS CODE = 200,服务端忽略应答包具体内容,为了协议友好,建议客户应答内容携带 JSON: {"code":0}。

参数说明

事件回调消息的 header 中包含以下字段:

字段名
Content-Type
application/json
Sign
签名值
SdkAppId
sdk application id

事件回调消息的 body 中包含以下字段:

字段名
类型
含义
EventGroupId
Number
事件组 ID, 云端录制固定为3
EventType
Number
回调通知的事件类型
CallbackTs
Number
事件回调服务器向您的服务器发出回调请求的 Unix 时间戳,单位为毫秒
EventInfo
JSON Object
事件信息

事件类型说明:

字段名
类型
含义
EVENT_TYPE_CLOUD_RECORDING_RECORDER_START
301
云端录制录制模块启动
EVENT_TYPE_CLOUD_RECORDING_RECORDER_STOP
302
云端录制录制模块退出
EVENT_TYPE_CLOUD_RECORDING_UPLOAD_START
303
云端录制文件上传任务启动,仅在选择对象存储时回调
EVENT_TYPE_CLOUD_RECORDING_FILE_INFO
304
云端录制 生成 m3u8 索引文件,第一次生成并且上传成功后回调,仅在通过 API 录制 选择对象存储时回调
EVENT_TYPE_CLOUD_RECORDING_UPLOAD_STOP
305
云端录制文件上传结束,仅在选择对象存储时回调
EVENT_TYPE_CLOUD_RECORDING_FAILOVER
306
云端录制发生迁移,原有的录制任务被迁移到新负载上时触发
EVENT_TYPE_CLOUD_RECORDING_FILE_SLICE
307
云端录制 生成 m3u8 索引文件(切出第一个 ts 切片)生成后回调,仅在通过 API 录制 选择对象存储时回调
EVENT_TYPE_CLOUD_RECORDING_DOWNLOAD_IMAGE_ERROR
309
云端录制下载解码图片文件发生错误
EVENT_TYPE_CLOUD_RECORDING_MP4_STOP
310
云端录制 MP4 录制任务结束,仅在通过 API录制 选择对象存储时回调(控制台开启自动录制,选择授权给点播的 cos 作为存储时,请关注311事件)
EVENT_TYPE_CLOUD_RECORDING_VOD_COMMIT
311
云端录制 VOD 录制任务上传媒体资源完成,在选择云点播时和通过控制台自动录制存储至 cos 时回调(录制文件结束后携带点播索引信息,请订阅此类型回调事件)
EVENT_TYPE_CLOUD_RECORDING_VOD_STOP
312
云端录制 VOD 录制任务结束,仅在选择云点播时回调
注意:
301-309区间的回调状态为实时录制的中间状态,可以更加清晰的知晓录制任务的进行过程并记录状态,实际录制文件上传到点播成功会回调311事件,整体任务结束回调312事件。

事件信息说明:

字段名
类型
含义
RoomId
String/Number
房间名(类型与客户端房间号类型一致)
EventTs
Number
时间发生的 Unix 时间戳,单位为秒 (不建议使用该字段,建议使用EventMsTs)
EventMsTs
Number
事件发生的 Unix 时间戳,单位为毫秒
UserId
String
录制机器人的用户 ID
TaskId
String
录制 ID,一次云端录制任务唯一的 ID
Payload
JsonObject
根据不同事件类型定义不同

事件类型为301
(EVENT_TYPE_CLOUD_RECORDING_RECORDER_START)时 Payload 的定义:
字段名
类型
含义
Status
Number
0:代表录制模块启动成功
1:代表录制模块启动失败
{
"EventGroupId": 3,
"EventType": 301,
"CallbackTs": 1622186275913,
"EventInfo": {
"RoomId": "xx",
"EventTs": "1622186275",
"EventMsTs": 1622186275757,
"UserId": "xx",
"TaskId": "xx",
"Payload": {
"Status": 0
}
}
}

事件类型为302
(EVENT_TYPE_CLOUD_RECORDING_RECORDER_STOP)时 Payload 的定义:
字段名
类型
含义
LeaveCode
Number
0:代表录制模块正常调用停止录制退出
1:录制机器人被客户踢出房间
2:客户解散房间
3:服务器将录制机器人踢出
4:服务器解散房间
99:代表房间内除了录制机器人没有其他用户流,超过指定时间退出
100:房间超时退出
101:同一用户重复进入相同房间导致机器人退出
{
"EventGroupId": 3,
"EventType": 302,
"CallbackTs": 1622186354806,
"EventInfo": {
"RoomId": "xx",
"EventTs": "1622186354",
"EventMsTs": 1622186275757,
"UserId": "xx",
"TaskId": "xx",
"Payload": {
"LeaveCode": 0
}
}
}

事件类型为303
(EVENT_TYPE_CLOUD_RECORDING_UPLOAD_START)时 Payload 的定义:
字段名
类型
含义
Status
Number
0:代表上传模块正常启动 1:代表上传模块初始化失败。

事件类型为304
(EVENT_TYPE_CLOUD_RECORDING_FILE_INFO )时 Payload 的定义:
字段名
类型
含义
FileList
String
生成的 M3U8 文件名

事件类型为305
(EVENT_TYPE_CLOUD_RECORDING_UPLOAD_STOP)时 Payload 的定义:
字段名
类型
含义
LeaveCode
Number
0:代表此次录制上传任务已经完成,所有的文件均已上传到指定的第三方云存储 1:代表此次录制上传任务已经完成,但至少有一片文件滞留在服务器或者备份存储上 2:代表滞留在服务器或者备份存储上的文件已经恢复上传到指定的第三方云存储
注意:305代表hls文件上传结束事件

事件类型为306
(EVENT_TYPE_CLOUD_RECORDING_FAILOVER)时 Payload 的定义:
字段名
类型
含义
Status
Number
0:代表此次迁移已经完成
{
"EventGroupId": 3,
"EventType": 306,
"CallbackTs": 1622191989674,
"EventInfo": {
"RoomId": "20015",
"EventTs": 1622191989,
"EventMsTs": 1622186275757,
"UserId": "xx",
"TaskId": "xx",
"Payload": {
"Status": 0
}
}
}

事件类型为307
(EVENT_TYPE_CLOUD_RECORDING_FILE_SLICE)时 Payload 的定义:
字段名
类型
含义
FileName
String
m3u8 文件名
UserId
String
录制文件对应的用户 ID
TrackType
String
音视频类型 audio/video/audio_video
BeginTimeStamp
String
录制开始时,服务器Unix时间戳(毫秒)

事件类型为309
(EVENT_TYPE_CLOUD_RECORDING_DOWNLOAD_IMAGE_ERROR)时 Payload 的定义:
字段名
类型
含义
Url
String
下载失败的 URL
{
"EventGroupId": 3,
"EventType": 309,
"CallbackTs": 1622191989674,
"EventInfo": {
"RoomId": "20015",
"EventTs": 1622191989,
"EventMsTs": 1622186275757,
"UserId": "xx",
"TaskId": "xx",
"Payload": {
"Url": "http://xx"
}
}
}

事件类型为310
(EVENT_TYPE_CLOUD_RECORDING_MP4_STOP)时 Payload 的定义:
说明:
310 是上传mp4文件到客户指定第三方云存储COS完成后的回调事件,一个录制任务可能会回调多个310事件(每个事件对应一个录制文件信息)
段名
类型
含义
Status
Number
0:代表此次录制 mp4 任务已经正常退出,所有的文件均已上传到指定的第三方云存储
1:代表此次录制 mp4 任务已经正常退出,但至少有一片文件滞留在服务 器或者备份存储上  2:代表此次录制 mp4 任务异常退出(可能原因是拉取 cos 的 hls 文件失败)
FileList
Array
所有生成的 mp4 文件名
FileMessage
Array
所有生成的 mp4 文件信息
FileName
String
mp4 文件名
UserId
String
mp4 文件对应的用户 ID(当录制模式为混流模式时,此字段为空)
TrackType
String
audio 音频 / video 纯视频 / audio_video 音视频
MediaId
String
主辅流标识,main 代表主流(摄像头),aux 代表辅流(屏幕分享),mix 代表混流录制
StartTimeStamp
Number
mp4 文件开始的 Unix 时间戳(毫秒)
EndTimeStamp
Number
mp4 文件结束的 Unix 时间戳(毫秒)
{
"EventGroupId": 3,
"EventType": 310,
"CallbackTs": 1622191965320,
"EventInfo": {
"RoomId": "20015",
"EventTs": 1622191989,
"EventMsTs": 1622186275757,
"UserId": "xx",
"TaskId": "xx",
"Payload": {
"Status": 0,
"FileList": ["xxxx1.mp4", "xxxx2.mp4"],
"FileMessage": [
{
"FileName": "xxxx1.mp4",
"UserId": "xxxx",
"TrackType": "audio_video",
"MediaId": "main",
"StartTimeStamp": 1622186279145,
"EndTimeStamp": 1622186282145
},
{
"FileName": "xxxx2.mp4",
"UserId": "xxxx",
"TrackType": "audio_video",
"MediaId": "main",
"StartTimeStamp": 1622186279153,
"EndTimeStamp": 1622186282153
}
]

}
}
}

事件类型为311
(EVENT_TYPE_CLOUD_RECORDING_VOD_COMMIT)时 Payload 的定义:
字段名
类型
含义
Status
Number
0:代表本录制文件正常上传至点播平台
1:代表本录制文件滞留在服务器或者备份存储上
2:代表本录制文件上传点播任务异常
UserId
String
本录制文件对应的用户 ID(当录制模式为合流模式时,此字段为空)
TrackType
String
audio 音频 / video 纯视频 / audio_video 音视频
MediaId
String
主辅流标识,main代表主流(摄像头),aux代表辅流(屏幕分享),mix代表混流录制
FileId
String
本录制文件在点播平台的唯一 ID
VideoUrl
String
本录制文件在点播平台的播放地址
CacheFile
String
本录制文件对应的 MP4/HLS 文件名
StartTimeStamp
Number
本录制文件开始的 UNIX 时间戳(毫秒)
EndTimeStamp
Number
本录制文件结束的 UNIX 时间戳(毫秒)
Errmsg
String
statue 不为0时,对应的错误信息
上传成功的回调:
{
"EventGroupId": 3,
"EventType": 311,
"CallbackTs": 1622191965320,
"EventInfo": {
"RoomId": "20015",
"EventTs": 1622191965,
"EventMsTs": 1622186275757,
"UserId": "xx",
"TaskId": "xx",
"Payload": {
"Status": 0,
"TencentVod": {
"UserId": "xx",
"TrackType": "audio_video",
"MediaId": "main",
"FileId": "xxxx",
"VideoUrl": "http://xxxx",
"CacheFile": "xxxx.mp4",
"StartTimeStamp": 1622186279153,
"EndTimeStamp": 1622186282153
}
}
}
}
上传失败的回调:
{
"EventGroupId": 3,
"EventType": 311,
"CallbackTs": 1622191965320,
"EventInfo": {
"RoomId": "20015",
"EventTs": 1622191965,
"EventMsTs": 1622186275757,
"UserId": "xx",
"TaskId": "xx",
"Payload": {
"Status": 1,
"Errmsg": "xxx",
"TencentVod": {
"UserId": "123",
"TrackType": "audio_video",
"CacheFile": "xxx.mp4"
}
}
}
}
说明:
录制完成收到311回调后到文件上传完成,根据您本次录制文件的大小不同可能需等待30s-3min。

事件类型为312
(EVENT_TYPE_CLOUD_RECORDING_VOD_STOP)时 Payload 的定义:
字段名
类型
含义
Status
Number
0:代表本次上传 VOD 任务已经正常退出
1:代表本次上传 VOD 任务异常退出
{
"EventGroupId": 3,
"EventType": 312,
"CallbackTs": 1622191965320,
"EventInfo": {
"RoomId": "20015",
"EventTs": 1622191965,
"EventMsTs": 1622186275757,
"UserId": "xx",
"TaskId": "xx",
"Payload": {
"Status": 0
}
}
}

计算签名

签名由 HMAC SHA256 加密算法计算得出,您的事件回调接收服务器收到回调消息后,通过同样的方式计算出签名,相同则说明是腾讯云的实时音视频的事件回调,没有被伪造。签名的计算如下所示:
//签名 Sign 计算公式中 key 为计算签名 Sign 用的加密密钥。
Sign = base64(hmacsha256(key, body))
注意:
body 为您收到回调请求的原始包体,不要做任何转化,示例如下:
body="{\n\t\"EventGroupId\":\t1,\n\t\"EventType\":\t103,\n\t\"CallbackTs\":\t1615554923704,\n\t\"EventInfo\":\t{\n\t\t\"RoomId\":\t12345,\n\t\t\"EventTs\":\t1608441737,\n\t\t\"UserId\":\t\"test\",\n\t\t\"UniqueId\":\t1615554922656,\n\t\t\"Role\":\t20,\n\t\t\"Reason\":\t1\n\t}\n}"

签名校验示例

Java
Python
PHP
Golang
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
//# 功能:第三方回调sign校验
//# 参数:
//#   key:控制台配置的密钥key
//#   body:腾讯云回调返回的body体
//#   sign:腾讯云回调返回的签名值sign
//# 返回值:
//#   Status:OK 表示校验通过,FAIL 表示校验失败,具体原因参考Info
//#   Info:成功/失败信息

public class checkSign {
    public static String getResultSign(String key, String body) throws Exception {
        Mac hmacSha256 = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
        hmacSha256.init(secret_key);
        return Base64.getEncoder().encodeToString(hmacSha256.doFinal(body.getBytes()));
    }
    public static void main(String[] args) throws Exception {
        String key = "123654";
        String body = "{\n" + "\t\"EventGroupId\":\t2,\n" + "\t\"EventType\":\t204,\n" + "\t\"CallbackTs\":\t1664209748188,\n" + "\t\"EventInfo\":\t{\n" + "\t\t\"RoomId\":\t8489,\n" + "\t\t\"EventTs\":\t1664209748,\n" + "\t\t\"EventMsTs\":\t1664209748180,\n" + "\t\t\"UserId\":\t\"user_85034614\",\n" + "\t\t\"Reason\":\t0\n" + "\t}\n" + "}";
        String Sign = "kkoFeO3Oh2ZHnjtg8tEAQhtXK16/KI05W3BQff8IvGA=";
        String resultSign = getResultSign(key, body);

        if (resultSign.equals(Sign)) {
            System.out.println("{'Status': 'OK', 'Info': '校验通过'}");
        } else {
            System.out.println("{'Status': 'FAIL', 'Info': '校验失败'}");
        }
    }
}
# -*- coding: utf8 -*-
import hmac
import base64
from hashlib import sha256

# 功能:第三方回调sign校验
# 参数:
#   key:控制台配置的密钥key
#   body:腾讯云回调返回的body体
#   sign:腾讯云回调返回的签名值sign
# 返回值:
#   Status:OK 表示校验通过,FAIL 表示校验失败,具体原因参考Info
#   Info:成功/失败信息

def checkSign(key, body, sign):
    temp_dict = {}
    computSign = base64.b64encode(hmac.new(key.encode('utf-8'), body.encode('utf-8'), digestmod=sha256).digest()).decode('utf-8')
    print(computSign)
    if computSign == sign:
        temp_dict['Status'] = 'OK'
        temp_dict['Info'] = '校验通过'
        return temp_dict
    else:
        temp_dict['Status'] = 'FAIL'
        temp_dict['Info'] = '校验失败'
        return temp_dict

if __name__ == '__main__':
    key = '123654'
    body = "{\n" + "\t\"EventGroupId\":\t2,\n" + "\t\"EventType\":\t204,\n" + "\t\"CallbackTs\":\t1664209748188,\n" + "\t\"EventInfo\":\t{\n" + "\t\t\"RoomId\":\t8489,\n" + "\t\t\"EventTs\":\t1664209748,\n" + "\t\t\"EventMsTs\":\t1664209748180,\n" + "\t\t\"UserId\":\t\"user_85034614\",\n" + "\t\t\"Reason\":\t0\n" + "\t}\n" + "}"
    sign = 'kkoFeO3Oh2ZHnjtg8tEAQhtXK16/KI05W3BQff8IvGA='
    result = checkSign(key, body, sign)
    print(result)
<?php

class TlsEventSig {
private $key = false;
private $body = false;
public function __construct( $key, $body ) {
$this->key = $key;
$this->body = $body;
}

private function __hmacsha256() {
$hash = hash_hmac( 'sha256', $this->body, $this->key, true );
return base64_encode( $hash);
}
public function genEventSig() {
return $this->__hmacsha256();
}
}

$key="789";
$data="{\n\t\"EventGroupId\":\t1,\n\t\"EventType\":\t101,\n\t\"CallbackTs\":\t1608086882372,\n\t\"EventInfo\":\t{\n\t\t\"RoomId\":\t20222,\n\t\t\"EventTs\":\t1608086882,\n\t\t\"UserId\":\t\"222222_phone\"\n\t}\n}";

$api = new TlsEventSig($key, $data);
echo $api->genEventSig();
package main
import "fmt"
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
)

func main () {
var data = "{\n\t\"EventGroupId\":\t1,\n\t\"EventType\":\t101,\n\t\"CallbackTs\":\t1608086882372,\n\t\"EventInfo\":\t{\n\t\t\"RoomId\":\t20222,\n\t\t\"EventTs\":\t1608086882,\n\t\t\"UserId\":\t\"222222_phone\"\n\t}\n}"
var key = "789"

//JSRUN引擎2.0,支持多达30种语言在线运行,全仿真在线交互输入输出。
fmt.Println(hmacsha256(data,key))
}

func hmacsha256(data string, key string) string {
h := hmac.New(sha256.New, []byte(key))
h.Write([]byte(data))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}