发布音视频流到直播 CDN回调

服务端转推回调支持将您使用 旁路转推 REST API 产生转推 CDN 的事件,以 HTTP/HTTPS 请求的形式通知到您的服务器。您可以向腾讯云提供相关的配置信息来开通该服务。

配置信息

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



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

超时重试

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

事件回调消息格式

事件回调消息以 HTTP/HTTPS POST 请求发送给您的服务器,其中:
字符编码格式:UTF-8。
请求:body 格式为 JSON。
应答:HTTP STATUS CODE = 200,服务端忽略应答包具体内容,为了协议友好,建议客户应答内容携带 JSON: {"code":0}。
包体示例:下述为“转推时间组-CDN 推流正在进行”事件的包体示例。
{ "EventGroupId": 4, "EventType": 401, "CallbackTs": 1622186275913, "EventInfo": { "RoomId": "xx", "RoomType": 1, "EventTsMs": 1622186275913, "UserId": "xx", "TaskId": "xx", "Payload": { "Url": "rtmp://tencent-url/xxxx" "Status": 2 /表示该转推任务正在向腾讯云CDN推流/ } } }

参数说明

回调消息参数

事件回调消息的 header 中包含以下字段:
字段名
Content-Type
application/json
Sign
签名值
SdkAppId
sdk application id
事件回调消息的 body 中包含以下字段:
字段名
类型
含义
EventGroupId
Number
事件组ID,混流转推事件固定为4
EventType
Number
回调通知的事件类型
CallbackTs
Number
事件回调服务器向您的服务器发出回调请求的 Unix 时间戳,单位为毫秒
EventInfo
JSON Object

事件组 ID

字段名
含义
EVENT_GROUP_CLOUD_PUBLISH
4
转推事件组

事件类型

字段名
含义
EVENT_TYPE_CLOUD_PUBLISH_CDN_STATUS
401
云端转推 CDN 状态回调

事件信息

字段名
类型
含义
RoomId
String/Number
房间名(类型与客户端房间号类型一致)
RoomType
Number
0表示数字房间号,1表示字符串房间号
EventMsTs
Number
事件发生的 Unix 时间戳,单位为毫秒
UserId
String
发起任务时指定的伴生机器人的用户 ID(AgentParams.UserId)
TaskId
Number
任务 ID
Payload
JSON Object
事件的详细信息

Payload(详细信息)

字段名
含义
Url
String
推流的目的 URL 地址
Status
Number
ErrorCode
Number
错误码
ErrorMsg
String
错误信息

转推状态

字段名
含义
回调频率
PUBLISH_CDN_STREAM_STATE_IDLE
0
推流未开始或已结束
仅回调1次
PUBLISH_CDN_STREAM_STATE_CONNECTING
1
正在连接 TRTC 服务器和 CDN 服务器
每5秒回调1次,60秒超时后不再回调
PUBLISH_CDN_STREAM_STATE_RUNNING
2
CDN 推流正在进行
仅回调1次
PUBLISH_CDN_STREAM_STATE_RECOVERING
3
TRTC 服务器和 CDN 服务器推流中断,正在恢复
每5秒回调1次,60秒超时后不再回调
PUBLISH_CDN_STREAM_STATE_FAILURE
4
TRTC 服务器和 CDN 服务器推流中断,且恢复或连接超时
仅回调1次
PUBLISH_CDN_STREAM_STATE_DISCONNECTING
5
正在断开 TRTC 服务器和 CDN 服务器
仅回调1次

转推状态推荐处理

状态
处理方法
PUBLISH_CDN_STREAM_STATE_IDLE
表示 URL 移除成功,无需处理。
PUBLISH_CDN_STREAM_STATE_CONNECTING
表示 URL 正在连接中,每隔5s回调一次,直到连接成功回调PUBLISH_CDN_STREAM_STATE_RUNNING,或者60s后回调PUBLISH_CDN_STREAM_STATE_FAILURE。您可以在收到PUBLISH_CDN_STREAM_STATE_FAILURE的时候替换有问题的 URL,调用UpdatePublishCdnStream更新 Publish 参数。
如果您的业务对时间比较敏感,可以在收到2个或以上的PUBLISH_CDN_STREAM_STATE_CONNECTING回调后,替换有问题的 URL,调用UpdatePublishCdnStream更新 Publish 参数。
PUBLISH_CDN_STREAM_STATE_RUNNING
表示 URL 推流成功,无需处理。
PUBLISH_CDN_STREAM_STATE_RECOVERING
表示推流过程发生了中断,正在重连中,每隔5s回调一次,直到重连成功回调PUBLISH_CDN_STREAM_STATE_RUNNING,或者60s后回调PUBLISH_CDN_STREAM_STATE_FAILURE。通常为网络抖动,无需处理。
如果PUBLISH_CDN_STREAM_STATE_RECOVERINGPUBLISH_CDN_STREAM_STATE_RUNNING短时间内交替出现,您需要检查下是否存在多任务使用相同的推流 URL 。
PUBLISH_CDN_STREAM_STATE_FAILURE
表示推流 URL ,在60s内建连失败或者恢复推流失败,此时您可以替换有问题的 URL ,调用UpdatePublishCdnStream更新 Publish 参数。
PUBLISH_CDN_STREAM_STATE_DISCONNECTING
表示,正在移除推流 URL ,移除成功后,会回调PUBLISH_CDN_STREAM_STATE_IDLE,无需处理。

基本回调转移示例

发起转推/新增转推地址到转推成功的事件转移 PUBLISH_CDN_STREAM_STATE_CONNECTING -> PUBLISH_CDN_STREAM_STATE_RUNNING
停止转推/删除转推地址到停止转推成功的事件转移 PUBLISH_CDN_STREAM_STATE_RUNNING -> PUBLISH_CDN_STREAM_STATE_DISCONNECTING -> PUBLISH_CDN_STREAM_STATE_IDLE
转推过程中,链接失败到重试链接成功的事件转移 PUBLISH_CDN_STREAM_STATE_RUNNING -> PUBLISH_CDN_STREAM_STATE_RECOVERING -> PUBLISH_CDN_STREAM_STATE_RUNNING
转推过程中,链接失败到重试链接超时失败的事件转移 PUBLISH_CDN_STREAM_STATE_RUNNING -> PUBLISH_CDN_STREAM_STATE_RECOVERING -> PUBLISH_CDN_STREAM_STATE_FAILURE->PUBLISH_CDN_STREAM_STATE_IDLE
注意:
推流回调有可能会乱序到达您的回调服务器,此时您需要根据 EventInfo 中的 EventMsTs 做事件排序,如果您只关心 URL 最新状态,可以忽略后续到达的过期事件。

计算签名

签名由 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))
}