feat(proto): add distribution model srs-hls/cf/cdn
This commit is contained in:
parent
077226df4c
commit
1f422c7340
|
|
@ -30,6 +30,13 @@ enum BackendKind {
|
|||
BACKEND_KIND_SRS = 2; // SRS(次级 / 本地对照)
|
||||
}
|
||||
|
||||
enum DistributionKind {
|
||||
DISTRIBUTION_KIND_UNSPECIFIED = 0;
|
||||
DISTRIBUTION_KIND_SRS_HLS = 1; // SRS HLS 直推(主干 remux 原地出)
|
||||
DISTRIBUTION_KIND_CF_SFU = 2; // Cloudflare SFU 分发(中继)
|
||||
DISTRIBUTION_KIND_CDN = 3; // 第三方直播 CDN 分发(RTMP/SRT Forward)
|
||||
}
|
||||
|
||||
message IceServer {
|
||||
repeated string urls = 1;
|
||||
string username = 2;
|
||||
|
|
@ -45,10 +52,26 @@ message BackendInfo {
|
|||
bool primary = 4;
|
||||
}
|
||||
|
||||
message DistributorInfo {
|
||||
DistributionKind kind = 1;
|
||||
string name = 2;
|
||||
bool configured = 3;
|
||||
bool enabled = 4;
|
||||
}
|
||||
|
||||
message DistributionTarget {
|
||||
DistributionKind kind = 1;
|
||||
string url = 2;
|
||||
string status = 3; // forwarding | ready | error
|
||||
int64 updated_at = 4;
|
||||
}
|
||||
|
||||
message GetConfigResponse {
|
||||
repeated BackendInfo backends = 1;
|
||||
string candidate = 2;
|
||||
bool token_required = 3;
|
||||
BackendInfo trunk = 4;
|
||||
repeated DistributorInfo distributors = 5;
|
||||
}
|
||||
|
||||
message StreamTarget {
|
||||
|
|
@ -63,6 +86,7 @@ message StreamTarget {
|
|||
message Room {
|
||||
string name = 1;
|
||||
repeated StreamTarget targets = 2;
|
||||
repeated DistributionTarget distributions = 3;
|
||||
}
|
||||
|
||||
message ListRoomsRequest {}
|
||||
|
|
@ -109,6 +133,7 @@ message WatchRoomRequest {
|
|||
message RoomEvent {
|
||||
string room = 1;
|
||||
repeated StreamTarget targets = 2;
|
||||
repeated DistributionTarget distributions = 3;
|
||||
}
|
||||
|
||||
// ===== Auth & RBAC (Casbin) =====
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -64,6 +64,10 @@ type Config struct {
|
|||
|
||||
AuthBossEnabled bool
|
||||
|
||||
Distributors string // 逗号分隔的分发方式,如 "srs-hls,cf,cdn"
|
||||
CDNRTMPURL string // 第三方 CDN RTMP 推流地址模板,如 rtmp://cdn.example.com/live
|
||||
CDNName string
|
||||
|
||||
DatabaseURL string // 嵌入式 DB DSN(TURSO_DATABASE_URL / DATABASE_URL),默认 file:./data/sync-live.db
|
||||
AuthToken string // 远程 libSQL 凭证,嵌入模式可为空
|
||||
}
|
||||
|
|
@ -123,6 +127,10 @@ func Load() *Config {
|
|||
|
||||
AuthBossEnabled: getenv("AUTHBOSS_ENABLED", "0") == "1",
|
||||
|
||||
Distributors: getenv("DISTRIBUTORS", "srs-hls,cf,cdn"),
|
||||
CDNRTMPURL: getenv("CDN_RTMP_URL", ""),
|
||||
CDNName: getenv("CDN_NAME", "cdn"),
|
||||
|
||||
DatabaseURL: getenv("TURSO_DATABASE_URL", "file:./data/sync-live.db?cache=shared&_journal_mode=WAL"),
|
||||
AuthToken: getenv("TURSO_AUTH_TOKEN", ""),
|
||||
}
|
||||
|
|
@ -167,6 +175,15 @@ func (c *Config) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) ValidateDistributors() error {
|
||||
for _, d := range c.DistributorList() {
|
||||
if d == "cdn" && c.CDNRTMPURL == "" {
|
||||
return fmt.Errorf("CDN 分发已启用但 CDN_RTMP_URL 未配置")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func getenv(k, def string) string {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
|
|
@ -221,6 +238,34 @@ func (c *Config) ProviderList() []string {
|
|||
return out
|
||||
}
|
||||
|
||||
func (c *Config) DistributorList() []string {
|
||||
parts := strings.Split(c.Distributors, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
seen := make(map[string]struct{}, len(parts))
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(strings.ToLower(p))
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
// normalize aliases
|
||||
if p == "srs" || p == "hls" {
|
||||
p = "srs-hls"
|
||||
}
|
||||
if p == "cloudflare" || p == "cf-sfu" {
|
||||
p = "cf"
|
||||
}
|
||||
if _, ok := seen[p]; !ok {
|
||||
seen[p] = struct{}{}
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *Config) CDNEnabled() bool {
|
||||
return c.CDNRTMPURL != ""
|
||||
}
|
||||
|
||||
func (c *Config) DSN() string {
|
||||
if c.AuthToken != "" && strings.HasPrefix(c.DatabaseURL, "libsql://") && !strings.Contains(c.DatabaseURL, "authToken") {
|
||||
sep := "?"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDistributorListParsing(t *testing.T) {
|
||||
c := &Config{Distributors: "srs-hls,cf,cdn"}
|
||||
got := c.DistributorList()
|
||||
if len(got) != 3 || got[0] != "srs-hls" {
|
||||
t.Fatalf("unexpected distributors %v", got)
|
||||
}
|
||||
if got[1] != "cf" || got[2] != "cdn" {
|
||||
t.Fatalf("unexpected order %v", got)
|
||||
}
|
||||
if c.CDNEnabled() {
|
||||
t.Fatalf("should not be enabled without CDN_RTMP_URL")
|
||||
}
|
||||
// alias normalization
|
||||
c2 := &Config{Distributors: "srs,cloudflare,cdn"}
|
||||
got2 := c2.DistributorList()
|
||||
if got2[0] != "srs-hls" || got2[1] != "cf" {
|
||||
t.Fatalf("alias normalize failed %v", got2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistributorListDedup(t *testing.T) {
|
||||
c := &Config{Distributors: "srs-hls,srs-hls,cf"}
|
||||
got := c.DistributorList()
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("want dedup 2, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCDNRequiresURLWhenEnabled(t *testing.T) {
|
||||
c := &Config{Distributors: "cdn", CDNRTMPURL: ""}
|
||||
if err := c.ValidateDistributors(); err == nil {
|
||||
t.Fatal("expected error when cdn enabled without URL")
|
||||
}
|
||||
c2 := &Config{Distributors: "cdn", CDNRTMPURL: "rtmp://cdn.example.com/live"}
|
||||
if err := c2.ValidateDistributors(); err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
c3 := &Config{Distributors: "srs-hls,cf", CDNRTMPURL: ""}
|
||||
if err := c3.ValidateDistributors(); err != nil {
|
||||
t.Fatalf("should not require CDN URL when cdn not in list, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCDNEnabled(t *testing.T) {
|
||||
c := &Config{CDNRTMPURL: ""}
|
||||
if c.CDNEnabled() {
|
||||
t.Fatal("empty should be disabled")
|
||||
}
|
||||
c2 := &Config{CDNRTMPURL: "rtmp://a/b"}
|
||||
if !c2.CDNEnabled() {
|
||||
t.Fatal("non-empty should be enabled")
|
||||
}
|
||||
}
|
||||
|
|
@ -16,10 +16,32 @@ export interface BackendInfo {
|
|||
primary: boolean
|
||||
}
|
||||
|
||||
export type DistributionKind =
|
||||
| 'DISTRIBUTION_KIND_UNSPECIFIED'
|
||||
| 'DISTRIBUTION_KIND_SRS_HLS'
|
||||
| 'DISTRIBUTION_KIND_CF_SFU'
|
||||
| 'DISTRIBUTION_KIND_CDN'
|
||||
|
||||
export interface DistributorInfo {
|
||||
kind: DistributionKind
|
||||
name: string
|
||||
configured: boolean
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface DistributionTarget {
|
||||
kind: DistributionKind
|
||||
url: string
|
||||
status: string
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export interface GetConfigResponse {
|
||||
backends: BackendInfo[]
|
||||
candidate: string
|
||||
token_required: boolean
|
||||
trunk?: BackendInfo
|
||||
distributors?: DistributorInfo[]
|
||||
}
|
||||
|
||||
export interface StreamTarget {
|
||||
|
|
@ -60,6 +82,7 @@ export interface AutoPlaySettings {
|
|||
export interface Room {
|
||||
name: string
|
||||
targets: StreamTarget[]
|
||||
distributions?: DistributionTarget[]
|
||||
display_name?: string
|
||||
description?: string
|
||||
creator?: string
|
||||
|
|
@ -127,6 +150,7 @@ export interface RoomMember {
|
|||
export interface RoomEvent {
|
||||
room: string
|
||||
targets: StreamTarget[]
|
||||
distributions?: DistributionTarget[]
|
||||
}
|
||||
|
||||
export interface PublishResponse {
|
||||
|
|
|
|||
Loading…
Reference in New Issue