使用 Token 鉴权
鉴权是指在用户访问你的系统前,对其进行身份校验。用户在使用声网服务,如加入音视频通话或登录信令系统时,声网使用 Token 对其鉴权。
本文展示如何为 AccessToken2 在服务端部署一个 Token 生成器,以及如何搭建一个使用 Token 鉴权的客户端。
鉴权原理
下图展示了鉴权的基本流程:
- 客户端根据需要,向 App 服务端申请 Token。
- App 服务端生成并返回 Token。
- 客户端以用户 ID、频道名以及获取到的 Token 加入频道。
- 声网平台读取该 Token 中包含的信息,并进行校验。
- 客户端收到加入频道成功回调,并获取用户 ID。
- Token 最大有效期为 24 小时。当即将过期时,客户端会收到 Token 即将过期的回调。
- 此时,如果客户端需要继续进行音视频互动,需要申请新的 Token。
- App 服务端生成并返回 Token。
- 客户端更新 Token。
Token 过期时,SDK 会触发 Token 过期回调。收到该回调后,客户端需要从服务器获取新的 Token,再使用新的 Token 重新加入频道。
你需要自行实现步骤 1、2、3、7、8、9 的代码逻辑。
Token 包含以下信息:
- 你在声网控制台创建项目时生成的 App ID
- 频道名
- 用户 ID
- 用户权限,如是否能发流或收流
- Token 的过期时间
前提条件
开始前,请确保你的项目或使用的声网产品满足如下条件:
-
一个有效的声网账户。
-
已开启 App 证书的声网项目。
-
Golang 1.14 或以上版本,GO111MODULE 设置为开启。
注意如果你使用的是 Go 1.16 或以上版本,GO111MODULE 已默认开启。详情请参考 New module changes in Go 1.16。
实现鉴权流程
本节介绍如何使用声网提供的代码生成并提供 Token,对用户及其权限进行校验。
获取 App ID 及 App 证书
本节介绍如何获取生成 Token 所需的安全信息,如你的项目的 App ID 及 App 证书。
获取 App ID
声网会给每个项目自动分配一个 App ID 作为项目唯一标识。
在声网控制台的项目管理页面,找到你的项目,点击 App ID 右侧的图标,即可获取项目的 App ID。
获取 App 证书
参考以下步骤获取 App 证书:
-
在声网控制台的项目管理页面,找到你的项目,点击配置。
-
点击主要证书下面的复制图标,即可获取项目的 App 证书。
部署 Token 服务器
Token 需要在你的服务端部署生成。当客户端发送请求时,服务端部署的 Token 生成器会生成相应的 Token,再发送给客户端。
本节展示如何使用 Golang 在你的本地设备上搭建并运行一个 Token 服务器。
此示例服务器使用 BuildTokenWithUid
[1/2]。
此示例服务器仅用于演示,请勿用于生产环境中。
-
创建一个
server.go
文件,然后贴入如下代码。将其中的<Your App ID>
和<Your App Certificate>
替换为你的 App ID 和 App 证书。Gopackage main
import (
rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
"fmt"
"log"
"net/http"
"encoding/json"
"errors"
"strconv"
)
type rtc_int_token_struct struct {
Uid_rtc_int uint32 `json:"uid"`
Channel_name string `json:"ChannelName"`
Role uint32 `json:"role"`
}
var rtc_token string
var int_uid uint32
var channel_name string
var role_num uint32
var role rtctokenbuilder.Role
// 使用 RtcTokenBuilder 来生成 RTC Token
func generateRtcToken(int_uid uint32, channelName string, role rtctokenbuilder.Role) {
appID := "<Your App ID>"
appCertificate := "<Your App Certificate>"
// AccessToken2 过期的时间,单位为秒
// 当 AccessToken2 过期但权限未过期时,用户仍在频道里并且可以发流,不会触发 SDK 回调
// 但一旦用户和频道断开连接,用户将无法使用该 Token 加入同一频道。请确保 AccessToken2 的过期时间晚于权限过期时间
tokenExpireTimeInSeconds := uint32(40)
// 权限过期的时间,单位为秒
// 权限过期30秒前会触发 token-privilege-will-expire 回调
// 权限过期时会触发 token-privilege-did-expire 回调
// 为作演示,在此将过期时间设为 40 秒。你可以看到客户端自动更新 Token 的过程
privilegeExpireTimeInSeconds := uint32(40)
result, err := rtctokenbuilder.BuildTokenWithUid(appID, appCertificate, channelName, int_uid, role, tokenExpireTimeInSeconds, privilegeExpireTimeInSeconds)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Token with uid: %s\n", result)
fmt.Printf("uid is %d\n", int_uid)
fmt.Printf("ChannelName is %s\n", channelName)
fmt.Printf("Role is %d\n", role)
}
rtc_token = result
}
func rtcTokenHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "*")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
if r.Method != "POST" && r.Method != "OPTIONS" {
http.Error(w, "Unsupported method. Please check.", http.StatusNotFound)
return
}
var t_int rtc_int_token_struct
var unmarshalErr *json.UnmarshalTypeError
int_decoder := json.NewDecoder(r.Body)
int_err := int_decoder.Decode(&t_int)
if int_err == nil {
int_uid = t_int.Uid_rtc_int
channel_name = t_int.Channel_name
role_num = t_int.Role
switch role_num {
case 1:
role = rtctokenbuilder.RolePublisher
case 2:
role = rtctokenbuilder.RoleSubscriber
}
}
if int_err != nil {
if errors.As(int_err, &unmarshalErr) {
errorResponse(w, "Bad request. Wrong type provided for field "+unmarshalErr.Value+unmarshalErr.Field+unmarshalErr.Struct, http.StatusBadRequest)
} else {
errorResponse(w, "Bad request.", http.StatusBadRequest)
}
return
}
generateRtcToken(int_uid, channel_name, role)
errorResponse(w, rtc_token, http.StatusOK)
log.Println(w, r)
}
func errorResponse(w http.ResponseWriter, message string, httpStatusCode int) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(httpStatusCode)
resp := make(map[string]string)
resp["token"] = message
resp["code"] = strconv.Itoa(httpStatusCode)
jsonResp, _ := json.Marshal(resp)
w.Write(jsonResp)
}
func main() {
// 使用 int 型 uid 生成 RTC Token
http.HandleFunc("/fetch_rtc_token", rtcTokenHandler)
fmt.Printf("Starting server at port 8082\n")
if err := http.ListenAndServe(":8082", nil); err != nil {
log.Fatal(err)
}
} -
go.mod
文件定义导入路径及依赖项。运行如下命令行来为你的 Token 服务器创建go.mod
文件:Go$ go mod init sampleServer
-
运行如下命令行安装依赖。你可以使用 Go 镜像进行加速,例如 https://goproxy.cn。
Go$ go get
-
运行如下命令行启动服务器:
Go$ go run server.go
使用 Token 对用户鉴权
本节展示如何使用 Token 对客户端的用户进行鉴权。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
using Agora.Rtc;
using Agora.Util;
using Logger = Agora.Util.Logger;
namespace Agora_RTC_Plugin.API_Example.Examples.Advanced.JoinChannelVideoToken
{
public class JoinChannelVideoToken : MonoBehaviour
{
// 填入你的 App ID 和频道名
public string appID = "";
public string channelName = "";
public uint localUid = 0;
internal static string channelToken = "";
internal IRtcEngine RtcEngine = null;
internal CONNECTION_STATE_TYPE _state = CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED;
private void Start()
{
if (CheckAppId())
{
InitEngine();
JoinChannel();
}
}
internal void RenewOrJoinToken(TokenObject tokenObject)
{
if (tokenObject == null || tokenObject.code != 200) {
Debug.Log("get token failed");
return;
}
JoinChannelVideoToken.channelToken = tokenObject.token;
if (_state == CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED
|| _state == CONNECTION_STATE_TYPE.CONNECTION_STATE_DISCONNECTED
|| _state == CONNECTION_STATE_TYPE.CONNECTION_STATE_FAILED
)
{
// 使用 Token 加入频道
JoinChannel();
}
else
{
// 在频道内更新 Token
UpdateToken();
}
}
private void Update()
{
PermissionHelper.RequestMicrophontPermission();
PermissionHelper.RequestCameraPermission();
}
private void UpdateToken()
{
RtcEngine.RenewToken(JoinChannelVideoToken.channelToken);
Debug.Log("renewToken: " + JoinChannelVideoToken.channelToken);
}
private bool CheckAppId()
{
if (appID.Length < 10) {
Debug.Log("Please fill in your appId in Inspector");
return false;
}
return true;
}
// 初始化
private void InitEngine()
{
RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
UserEventHandler handler = new UserEventHandler(this);
RtcEngineContext context = new RtcEngineContext(appID, 0,
CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING, null,
AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT);
RtcEngine.Initialize(context);
RtcEngine.InitEventHandler(handler);
}
// 加入频道
private void JoinChannel()
{
RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
RtcEngine.EnableAudio();
RtcEngine.EnableVideo();
if (channelToken.Length == 0)
{
StartCoroutine(HelperClass.FetchToken(this.localUid, channelName,CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, this.RenewOrJoinToken));
return;
}
RtcEngine.JoinChannel(channelToken, channelName, "");
Debug.Log("joinChannel with token:" + channelToken);
}
internal void StartUpdateTokenEveryTenSecond() {
InvokeRepeating("RequestToken", 10, 10);
}
private void RequestToken() {
StartCoroutine(HelperClass.FetchToken(this.localUid, channelName, CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, this.RenewOrJoinToken));
}
private void OnDestroy()
{
Debug.Log("OnDestroy");
if (RtcEngine == null) return;
RtcEngine.InitEventHandler(null);
RtcEngine.LeaveChannel();
RtcEngine.Dispose();
}
internal string GetChannelName()
{
return channelName;
}
#region -- Video Render UI Logic ---
internal static void MakeVideoView(uint uid, string channelId = "")
{
GameObject go = GameObject.Find(uid.ToString());
if (!ReferenceEquals(go, null))
{
return;
}
VideoSurface videoSurface = MakeImageSurface(uid.ToString());
if (!ReferenceEquals(videoSurface, null))
{
if (uid == 0)
{
videoSurface.SetForUser(uid, channelId);
}
else
{
videoSurface.SetForUser(uid, channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
}
videoSurface.OnTextureSizeModify += (int width, int height) =>
{
float scale = (float)height / (float)width;
videoSurface.transform.localScale = new Vector3(-5, 5 * scale, 1);
Debug.Log("OnTextureSizeModify: " + width + " " + height);
};
videoSurface.SetEnable(true);
}
}
private static VideoSurface MakePlaneSurface(string goName)
{
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Plane);
if (go == null)
{
return null;
}
go.name = goName;
go.transform.Rotate(-90.0f, 0.0f, 0.0f);
go.transform.position = Vector3.zero;
go.transform.localScale = new Vector3(0.25f, 0.5f, .5f);
var videoSurface = go.AddComponent<VideoSurface>();
return videoSurface;
}
private static VideoSurface MakeImageSurface(string goName)
{
GameObject go = new GameObject();
if (go == null)
{
return null;
}
go.name = goName;
go.AddComponent<RawImage>();
go.AddComponent<UIElementDrag>();
GameObject canvas = GameObject.Find("VideoCanvas");
if (canvas != null)
{
go.transform.parent = canvas.transform;
Debug.Log("add video view");
}
else
{
Debug.Log("Canvas is null video view");
}
go.transform.Rotate(0f, 0.0f, 180.0f);
go.transform.localPosition = Vector3.zero;
go.transform.localScale = new Vector3(3f, 4f, 1f);
var videoSurface = go.AddComponent<VideoSurface>();
return videoSurface;
}
internal static void DestroyVideoView(uint uid)
{
GameObject go = GameObject.Find(uid.ToString());
if (!ReferenceEquals(go, null))
{
Object.Destroy(go);
}
}
#endregion
}
internal class UserEventHandler : IRtcEngineEventHandler
{
private readonly JoinChannelVideoToken _helloVideoTokenAgora;
internal UserEventHandler(JoinChannelVideoToken helloVideoTokenAgora)
{
_helloVideoTokenAgora= helloVideoTokenAgora;
}
public override void OnError(int err, string msg)
{
Debug.Log(string.Format("OnError err: {0}, msg: {1}", err, msg));
}
// 加入频道成功
public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
{
int build = 0;
Debug.Log(string.Format("sdk version: ${0}",
_helloVideoTokenAgora.RtcEngine.GetVersion(ref build)));
Debug.Log(
string.Format("OnJoinChannelSuccess channelName: {0}, uid: {1}, elapsed: {2}",
connection.channelId, connection.localUid, elapsed));
Debug.Log(string.Format("New Token: {0}",
JoinChannelVideoToken.channelToken));
JoinChannelVideoToken.MakeVideoView(0);
_helloVideoTokenAgora.localUid = connection.localUid;
_helloVideoTokenAgora.StartUpdateTokenEveryTenSecond();
}
public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
{
Debug.Log(string.Format("OnUserJoined uid: ${0} elapsed: ${1}", uid,
elapsed));
JoinChannelVideoToken.MakeVideoView(uid, _helloVideoTokenAgora.GetChannelName());
}
public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
{
Debug.Log(string.Format("OnUserOffLine uid: ${0}, reason: ${1}", uid,
(int)reason));
JoinChannelVideoToken.DestroyVideoView(uid);
}
// Token 即将过期
public override void OnTokenPrivilegeWillExpire(RtcConnection connection, string token)
{
_helloVideoTokenAgora.StartCoroutine(HelperClass.FetchToken(_helloVideoTokenAgora.localUid,
_helloVideoTokenAgora.GetChannelName(), CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER, _helloVideoTokenAgora.RenewOrJoinToken));
}
public override void OnConnectionStateChanged(RtcConnection connection, CONNECTION_STATE_TYPE state,
CONNECTION_CHANGED_REASON_TYPE reason)
{
_helloVideoTokenAgora._state = state;
}
public override void OnConnectionLost(RtcConnection connection)
{
Debug.Log(string.Format("OnConnectionLost "));
}
}
#endregion
}
namespace Agora.Util
{
public static class HelperClass
{
public static IEnumerator FetchToken(uint uid, string channelName, CLIENT_ROLE_TYPE role, Action<TokenObject> callback = null)
{
Dictionary<string, object> postParams = new Dictionary<string, object>();
postParams.Add("uid", uid);
postParams.Add("ChannelName", channelName);
postParams.Add("role", role);
string json = AgoraJson.ToJson<Dictionary<string, object>>(postParams);
var url = "http://127.0.0.1/fetch_rtc_token";
byte[] postBytes = System.Text.Encoding.Default.GetBytes(json);
UnityWebRequest request = new UnityWebRequest(url, "POST");
request.uploadHandler = (UploadHandler)new UploadHandlerRaw(postBytes);
request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
Debug.Log(request.error);
callback(null);
yield break;
}
Debug.Log("text: " + request.downloadHandler.text);
TokenObject tokenInfo = JsonUtility.FromJson<TokenObject>(request.downloadHandler.text);
callback(tokenInfo);
}
}
}
运行项目
在本地设备的模拟器中构建并运行项目,App 会执行如下操作:
- 加入频道。
- 每隔 10 秒更新 Token。
参考
本节介绍 Token 生成器代码库、使用 Token 的版本要求等相关文档。
对 SDK 的兼容性要求
AccessToken2 支持以下版本的声网 SDK(不包括客户端旁路推流功能):
SDK | 版本 |
---|---|
RTC Native 4.x SDK | ≥ v4.0.0 |
RTC Electron 4.x SDK | ≥ v4.0.0 |
RTC Unity 4.x SDK | ≥ v4.0.0 |
RTC React Native 4.x SDK | ≥ v4.0.0 |
RTC Flutter 4.x SDK | ≥ v6.0.0 |
RTC Native 3.x SDK | ≥ 3.6.0 |
RTC Electron 3.x SDK | ≥ 3.6.0 |
RTC Unity 3.x SDK | ≥ 3.6.0 |
RTC React Native 3.x SDK | ≥ 3.6.0 |
RTC Flutter 3.x SDK | ≥ 5.10 |
RTC Web SDK | ≥ v4.8.0 |
使用 AccessToken2 的 RTC SDK 可与使用 AccessToken 的 RTC SDK 互通。支持 AccessToken2 的 RTC SDK 也支持 AccessToken。
Token 生成器代码
声网在 GitHub 上提供一个开源的 AgoraDynamicKey 仓库,支持使用 C++、Java、Go 等语言在你自己的服务器上生成 AccessToken2。
语言 | 算法 | 核心方法 | 示例代码 |
---|---|---|---|
C++ | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.cpp |
Go | HMAC-SHA256 | buildTokenWithUid | sample.go |
Java | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.java |
Node.js | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.js |
PHP | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.php |
Python | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.py |
Python3 | HMAC-SHA256 | buildTokenWithUid | RtcTokenBuilder2Sample.py |
BuildTokenWithUid API 参考
本节介绍生成 AccessToken2 的核心方法 BuildTokenWithUid
。AccessToken2 生成器代码中提供两个 BuildTokenWithUid
方法:
-
BuildTokenWithUid
[1/2]:生成 AccessToken2,并设置 AccessToken2 和所有权限的过期时间。 -
BuildTokenWithUid
[2/2]:生成 AccessToken2,并设置如下过期时间:- AccessToken2 过期时间
- 加入频道权限的过期时间
- 频道内发布音频流权限的过期时间
- 频道内发布视频流权限的过期时间
- 频道内发布数据流权限的过期时间
BuildTokenWithUid [1/2]
该方法使用 token_expire
参数设置 AccessToken2 的过期时间,使用 privilege_expire
参数设置所有权限的过期时间。
// 以 C++ 为例
static std::string BuildTokenWithUid(const std::string& app_id,
const std::string& app_certificate,
const std::string& channel_name,
uint32_t uid,
UserRole role,
uint32_t token_expire,
uint32_t privilege_expire = 0);
参数 | 描述 |
---|---|
app_id | 你在声网控制台创建项目时生成的 App ID。 |
app_certificate | 你的项目的 App 证书。 |
channel_name | 频道名称,长度在 64 个字节以内。以下为支持的字符集范围:
|
uid | 待鉴权用户的用户 ID 32 位无符号整数,范围为1到 (2³² - 1), 并保证唯一性。 如不需对用户 ID 进行鉴权,即客户端使用任何 uid 都可加入频道,请把 uid 设为 0。 |
role | 权限,分为发流用户和接收用户。参数决定用户是否能在频道中发流。kRolePublisher (1):(默认)用户有发流权限,即用户可在频道中发流。kRoleSubscriber (2):用户有接收权限,即用户可在频道中接收,但不可发流。该参数仅在开启连麦鉴权后才生效。详情参考开启连麦鉴权。 |
token_expire | AccessToken2 从生成到过期的时间长度,单位为秒。例如,如果你将 token_expire 设为 600 ,则 AccessToken2 会在生成后 10 分钟过期。AccessToken2 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间,AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0,AccessToken2 立即过期。 |
privilege_expire | 从 AccessToken2 生成到所有权限过期的时间长度,单位为秒。例如,如果你将 privilege_expire 设为 600 ,则权限会在生成后 10 分钟过期。如果你将此参数设为 0(默认值),则权限永不过期。 |
当 AccessToken2 过期但权限未过期时,用户仍在频道里并且可以发流,不会触发 SDK 中与 Token 相关的任何回调。但是一旦和频道断开连接,用户将无法使用该 Token 加入同一频道。因此,确保 AccessToken2 的过期时间晚于权限过期时间。
BuildTokenWithUid [2/2]
为支持在权限级别设置过期时间,声网还提供 BuildTokenWithUid
的同名重载方法。支持你设置 AccessToken2 的过期时间以及以下权限的过期时间:
- 加入频道
- 频道内发布音频流
- 频道内发布视频流
- 频道内发布数据流
// 以 C++ 为例
static std::string BuildTokenWithUid(
const std::string& app_id,
const std::string& app_certificate,
const std::string& channel_name,
uint32_t uid,
uint32_t token_expire,
uint32_t join_channel_privilege_expire = 0,
uint32_t pub_audio_privilege_expire = 0,
uint32_t pub_video_privilege_expire = 0,
uint32_t pub_data_stream_privilege_expire = 0);
该方法生成 RTC AccessToken2,支持你设置 AccessToken2 的过期时间以及以下权限的过期时间:
- 加入 RTC 频道
- 在 RTC 频道中发布音频流
- 在 RTC 频道中发布视频流
- 在 RTC 频道中发布数据流
其中,在 RTC 频道中发布音频流、视频流和数据流的权限仅在开启连麦鉴权服务后才生效。
一个用户可以设置多个权限。每个权限的最大有效时间为 24 小时。权限即将过期或已经过期后,SDK 会分别触发 onTokenPriviegeWillExpire
或 onRequestToken
回调。你需要在 App 逻辑中添加如下操作:
- 识别即将过期或已经过期的是哪类权限。
- App 从 Token 服务器获取新的 AccessToken2。
- SDK 调用
renewToken
以更新 AccessToken2。
你需要根据实际业务场景设置合理的过期时间,并确保 AccessToken2 的过期时间晚于其他权限过期时间。例如,如果加入频道的权限过期时间早于发布音频流权限的过期时间,则在加入频道的权限过期后,用户就会被踢出 RTC 频道。当 AccessToken2 过期但加入频道的权限未过期时,用户仍在频道里,并且只要发流权限未过期就可以发流,不会触发 SDK 中与 Token 相关的任何回调。但一旦和频道断开连接,用户将无法使用该 Token 加入同一频道。
参数 | 描述 |
---|---|
token_expire | AccessToken2 从生成到过期的时间长度,单位为秒。例如,如果你将 token_expire 设为 600 ,则 AccessToken2 会在生成后 10 分钟过期。Token 的最大有效期为 24 小时。 如果你将此参数设为超过 24 小时的时间,AccessToken2 有效期依然为 24 小时。如果你将此参数设为 0,AccessToken2 立即过期。 |
join_channel_privilege_expire | 从 AccessToken2 生成到加入频道权限过期的时间长度,单位为秒。例如,如果你将此参数为 600 ,则加入频道权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0(默认值),则加入频道权限永不过期。 |
pub_audio_privilege_expire | 从 AccessToken2 生成到在频道内发布音频流的权限过期的时间长度,单位为秒。例如,如果你将此参数为 600 ,则发布语音流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0(默认值),则发布语音流权限永不过期。 |
pub_video_privilege_expire | 从 AccessToken2 生成到在频道内发布视频流的权限过期的时间长度,单位为秒。例如,如果你将此参数为 600 ,则发布视频流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0(默认值),则发布视频流权限永不过期。 |
pub_data_stream_privilege_expire | 从 AccessToken2 生成到在频道内发布数据流的权限过期的时间长度,单位为秒。例如,如果你将此参数为 600 ,则发布数据流权限会在 AccessToken2 生成后 10 分钟过期。如果你将此参数设为 0(默认值),则发布数据流权限永不过期。 |
开启连麦鉴权
请参考以下步骤在声网控制台开启连麦鉴权服务:
- 登录声网控制台,在项目列表区域选择你想要开启的项目,点击配置按钮,进入编辑项目页面。
- 在项目详情页,下滑到功能区域,点击连麦鉴权区域的启用。
- 按照屏幕提示,了解开通该功能的主要事项,勾选后点击启用。
在控制台启用连麦鉴权后,该服务会在约 5 分钟后生效。
项目一旦开启了连麦鉴权服务,则用户在频道中发流需要同时满足两个条件:
- 在
setClientRole
中设置的role
参数为BROADCASTER
(1)。 - 使用 Token 加入频道的用户拥有发流的权限(在
BuildTokenWithUid
中设置的role
参数为kRolePublisher
)。