github.com/qiniu/go-sdk/v7@v7.27.0/rtc/api.go (about) 1 package rtc 2 3 import ( 4 "crypto/hmac" 5 "crypto/sha1" 6 "encoding/base64" 7 "encoding/json" 8 "fmt" 9 "net/http" 10 "strings" 11 "time" 12 13 "github.com/qiniu/go-sdk/v7/auth" 14 ) 15 16 // RtcHost 为 Qiniu RTC Server API服务域名 17 var RtcHost = "rtc.qiniuapi.com" 18 19 // Manager 提供了 Qiniu RTC Server API 相关功能 20 type Manager struct { 21 mac *auth.Credentials 22 httpClient *http.Client 23 } 24 25 // MergePublishRtmp 连麦合流转推 RTMP 的配置 26 // Enable: 布尔类型,用于开启和关闭所有房间的合流功能。 27 // AudioOnly: 布尔类型,可选,指定是否只合成音频。 28 // Height, Width: int,可选,指定合流输出的高和宽,默认为 640 x 480。 29 // OutputFps: int,可选,指定合流输出的帧率,默认为 25 fps 。 30 // OutputKbps: int,可选,指定合流输出的码率,默认为 1000 。 31 // URL: 合流后转推旁路直播的地址,可选,支持魔法变量配置按照连麦房间号生成不同的推流地址。如果是转推到七牛直播云,不建议使用该配置 32 // StreamTitle: 转推七牛直播云的流名,可选,支持魔法变量配置按照连麦房间号生成不同的流名。例如,配置 Hub 为 qn-zhibo ,配置 StreamTitle 为 $(roomName) ,则房间 meeting-001 的合流将会被转推到 rtmp://pili-publish.qn-zhibo.***.com/qn-zhibo/meeting-001地址。详细配置细则,请咨询七牛技术支持。 33 type MergePublishRtmp struct { 34 Enable bool `json:"enable,omitempty"` 35 AudioOnly bool `json:"audioOnly,omitempty"` 36 Height int `json:"height,omitempty"` 37 Width int `json:"width,omitempty"` 38 OutputFps int `json:"fps,omitempty"` 39 OutputKbps int `json:"kbps,omitempty"` 40 URL string `json:"url,omitempty"` 41 StreamTitle string `json:"streamTitle,omitempty"` 42 } 43 44 // App 完整信息 45 // AppID: app 的唯一标识,创建的时候由系统生成。 46 type App struct { 47 AppID string `json:"appId"` 48 AppInitConf 49 MergePublishRtmp MergePublishRtmp `json:"mergePublishRtmp,omitempty"` 50 CreatedAt time.Time `json:"createdAt"` 51 UpdatedAt time.Time `json:"updatedAt"` 52 } 53 54 // AppInitConf 创建 App 请求参数 55 // Title: app 的名称, 可选。 56 // Hub: 绑定的直播 hub,可选,用于合流后 rtmp 推流。 57 // MaxUsers: int 类型,可选,连麦房间支持的最大在线人数。 58 // NoAutoKickUser: bool 类型,可选,禁止自动踢人。 59 type AppInitConf struct { 60 Hub string `json:"hub,omitempty"` 61 Title string `json:"title,omitempty"` 62 MaxUsers int `json:"maxUsers,omitempty"` 63 NoAutoKickUser bool `json:"noAutoKickUser,omitempty"` 64 } 65 66 // MergePublishRtmpInfo 连麦合流转推 RTMP 的配置更改信息 67 type MergePublishRtmpInfo struct { 68 Enable *bool `json:"enable,omitempty"` 69 AudioOnly *bool `json:"audioOnly,omitempty"` 70 Height *int `json:"height,omitempty"` 71 Width *int `json:"width,omitempty"` 72 OutputFps *int `json:"fps,omitempty"` 73 OutputKbps *int `json:"kbps,omitempty"` 74 URL *string `json:"url,omitempty"` 75 StreamTitle *string `json:"streamTitle,omitempty"` 76 } 77 78 // AppUpdateInfo 更改信息 79 // MergePublishRtmpInfo 连麦合流转推 RTMP 的配置更改信息 80 type AppUpdateInfo struct { 81 Hub *string `json:"hub,omitempty"` 82 Title *string `json:"title,omitempty"` 83 MaxUsers *int `json:"maxUsers,omitempty"` 84 NoAutoKickUser *bool `json:"noAutoKickUser,omitempty"` 85 MergePublishRtmp *MergePublishRtmpInfo `json:"mergePublishRtmp,omitempty"` 86 } 87 88 // User 连麦房间里的用户 89 type User struct { 90 UserID string `json:"userId"` 91 } 92 93 // NewManager 用来构建一个新的 Manager 94 func NewManager(mac *auth.Credentials) *Manager { 95 httpClient := http.DefaultClient 96 return &Manager{mac: mac, httpClient: httpClient} 97 } 98 99 // CreateApp 新建实时音视频云 100 func (r *Manager) CreateApp(appReq AppInitConf) (App, error) { 101 url := buildURL("/v3/apps") 102 ret := App{} 103 info := postReq(r.httpClient, r.mac, url, &appReq, &ret) 104 return ret, info.Err 105 } 106 107 // GetApp 根据 appID 获取 实时音视频云 信息 108 func (r *Manager) GetApp(appID string) (App, error) { 109 url := buildURL("/v3/apps/" + appID) 110 ret := App{} 111 info := getReq(r.httpClient, r.mac, url, &ret) 112 return ret, info.Err 113 } 114 115 // DeleteApp 根据 appID 删除 实时音视频云 116 func (r *Manager) DeleteApp(appID string) error { 117 url := buildURL("/v3/apps/" + appID) 118 info := delReq(r.httpClient, r.mac, url, nil) 119 return info.Err 120 } 121 122 // UpdateApp 根据 appID, App 更改实时音视频云 信息 123 func (r *Manager) UpdateApp(appID string, appInfo AppUpdateInfo) (App, error) { 124 url := buildURL("/v3/apps/" + appID) 125 ret := App{} 126 info := postReq(r.httpClient, r.mac, url, &appInfo, &ret) 127 return ret, info.Err 128 } 129 130 // ListUser 根据 appID, roomName 获取连麦房间里在线的用户 131 // appID: 连麦房间所属的 app 。 132 // roomName: 操作所查询的连麦房间。 133 func (r *Manager) ListUser(appID, roomName string) ([]User, error) { 134 url := buildURL("/v3/apps/" + appID + "/rooms/" + roomName + "/users") 135 users := struct { 136 Users []User `json:"users"` 137 }{} 138 info := getReq(r.httpClient, r.mac, url, &users) 139 return users.Users, info.Err 140 } 141 142 // KickUser 根据 appID, roomName, UserID 剔除在线的用户 143 // appID: 连麦房间所属的 app 。 144 // roomName: 连麦房间。 145 // userID: 操作所剔除的用户。 146 func (r *Manager) KickUser(appID, roomName, userID string) error { 147 url := buildURL("/v3/apps/" + appID + "/rooms/" + roomName + "/users/" + userID) 148 info := delReq(r.httpClient, r.mac, url, nil) 149 return info.Err 150 } 151 152 // RoomQuery 房间查询响应结果 153 // IsEnd: bool 类型,分页查询是否已经查完所有房间。 154 // Offset: int 类型,下次分页查询使用的位移标记。 155 // Rooms: 当前活跃的房间名列表。 156 type RoomQuery struct { 157 IsEnd bool `json:"end"` 158 Offset int `json:"offset"` 159 Rooms []RoomName `json:"rooms"` 160 } 161 162 // RoomName 房间名 163 type RoomName string 164 165 // ListActiveRooms 根据 appID, roomNamePrefix, offset, limit 查询当前活跃的房间 166 // appID: 连麦房间所属的 app 。 167 // roomNamePrefix: 所查询房间名的前缀索引,可以为空。 168 // offset: int 类型,分页查询的位移标记。 169 // limit: int 类型,此次查询的最大长度。 170 func (r *Manager) ListActiveRooms(appID, roomNamePrefix string, offset, limit int) (RoomQuery, error) { 171 ret, _, err := r.doListActiveRoom(appID, roomNamePrefix, offset, limit) 172 return ret, err 173 } 174 175 // ListAllActiveRooms 根据 appID, roomNamePrefix 查询当前活跃的房间 176 // appID: 连麦房间所属的 app 。 177 // roomNamePrefix: 所查询房间名的前缀索引,可以为空。 178 func (r *Manager) ListAllActiveRooms(appID, roomNamePrefix string) ([]RoomName, error) { 179 ns := []RoomName{} 180 var outErr error 181 for offset := 0; ; { 182 q, info, err := r.doListActiveRoom(appID, roomNamePrefix, offset, 100) 183 if err != nil && info.Code != 401 { 184 time.Sleep(500 * time.Millisecond) 185 q, info, err = r.doListActiveRoom(appID, roomNamePrefix, offset, 100) 186 } 187 188 if err != nil || len(q.Rooms) == 0 { 189 outErr = err 190 break 191 } 192 offset = q.Offset 193 ns = append(ns, q.Rooms...) 194 if q.IsEnd { 195 break 196 } 197 } 198 return ns, outErr 199 } 200 201 func (r *Manager) doListActiveRoom(appID, roomNamePrefix string, offset, limit int) (RoomQuery, resInfo, error) { 202 query := "" 203 roomNamePrefix = strings.TrimSpace(roomNamePrefix) 204 if len(roomNamePrefix) != 0 { 205 query = "prefix=" + roomNamePrefix + "&" 206 } 207 query += fmt.Sprintf("offset=%v&limit=%v", offset, limit) 208 url := buildURL("/v3/apps/" + appID + "/rooms?" + query) 209 ret := RoomQuery{} 210 info := getReq(r.httpClient, r.mac, url, &ret) 211 return ret, *info, info.Err 212 } 213 214 // RoomAccess 房间管理凭证 215 // AppID: 房间所属帐号的 app 。 216 // RoomName: 房间名称,需满足规格 ^[a-zA-Z0-9_-]{3,64}$ 217 // UserID: 请求加入房间的用户 ID,需满足规格 ^[a-zA-Z0-9_-]{3,50}$ 218 // ExpireAt: int64 类型,鉴权的有效时间,传入以秒为单位的64位Unix绝对时间,token 将在该时间后失效。 219 // Permission: 该用户的房间管理权限,"admin" 或 "user",默认为 "user" 。当权限角色为 "admin" 时,拥有将其他用户移除出房间等特权. 220 type RoomAccess struct { 221 AppID string `json:"appId"` 222 RoomName string `json:"roomName"` 223 UserID string `json:"userId"` 224 ExpireAt int64 `json:"expireAt"` 225 Permission string `json:"permission"` 226 } 227 228 // GetRoomToken 生成房间管理鉴权,连麦用户终端通过房间管理鉴权获取七牛连麦服务。 229 func (r *Manager) GetRoomToken(roomAccess RoomAccess) (token string, err error) { 230 roomAccessByte, err := json.Marshal(roomAccess) 231 if err != nil { 232 return 233 } 234 buf := make([]byte, base64.URLEncoding.EncodedLen(len(roomAccessByte))) 235 base64.URLEncoding.Encode(buf, roomAccessByte) 236 237 hmacsha1 := hmac.New(sha1.New, r.mac.SecretKey) 238 hmacsha1.Write(buf) 239 sign := hmacsha1.Sum(nil) 240 241 encodedSign := base64.URLEncoding.EncodeToString(sign) 242 token = r.mac.AccessKey + ":" + encodedSign + ":" + string(buf) 243 return 244 }