From 1f422c73406cdecb550a7c7f325b52f7c43c6fdc Mon Sep 17 00:00:00 2001 From: noelorin Date: Sun, 23 Aug 2026 13:56:31 +0800 Subject: [PATCH] feat(proto): add distribution model srs-hls/cf/cdn --- api/sync_live.proto | 25 ++ gen/sync_live.pb.go | 557 ++++++++++++++++++++++++--------- internal/config/config.go | 45 +++ internal/config/config_test.go | 57 ++++ web/src/lib/types.ts | 24 ++ 5 files changed, 555 insertions(+), 153 deletions(-) create mode 100644 internal/config/config_test.go diff --git a/api/sync_live.proto b/api/sync_live.proto index 7c2aef7..d1b3f90 100644 --- a/api/sync_live.proto +++ b/api/sync_live.proto @@ -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) ===== diff --git a/gen/sync_live.pb.go b/gen/sync_live.pb.go index 628047b..e03f659 100644 --- a/gen/sync_live.pb.go +++ b/gen/sync_live.pb.go @@ -70,6 +70,58 @@ func (BackendKind) EnumDescriptor() ([]byte, []int) { return file_sync_live_proto_rawDescGZIP(), []int{0} } +type DistributionKind int32 + +const ( + DistributionKind_DISTRIBUTION_KIND_UNSPECIFIED DistributionKind = 0 + DistributionKind_DISTRIBUTION_KIND_SRS_HLS DistributionKind = 1 // SRS HLS 直推(主干 remux 原地出) + DistributionKind_DISTRIBUTION_KIND_CF_SFU DistributionKind = 2 // Cloudflare SFU 分发(中继) + DistributionKind_DISTRIBUTION_KIND_CDN DistributionKind = 3 // 第三方直播 CDN 分发(RTMP/SRT Forward) +) + +// Enum value maps for DistributionKind. +var ( + DistributionKind_name = map[int32]string{ + 0: "DISTRIBUTION_KIND_UNSPECIFIED", + 1: "DISTRIBUTION_KIND_SRS_HLS", + 2: "DISTRIBUTION_KIND_CF_SFU", + 3: "DISTRIBUTION_KIND_CDN", + } + DistributionKind_value = map[string]int32{ + "DISTRIBUTION_KIND_UNSPECIFIED": 0, + "DISTRIBUTION_KIND_SRS_HLS": 1, + "DISTRIBUTION_KIND_CF_SFU": 2, + "DISTRIBUTION_KIND_CDN": 3, + } +) + +func (x DistributionKind) Enum() *DistributionKind { + p := new(DistributionKind) + *p = x + return p +} + +func (x DistributionKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DistributionKind) Descriptor() protoreflect.EnumDescriptor { + return file_sync_live_proto_enumTypes[1].Descriptor() +} + +func (DistributionKind) Type() protoreflect.EnumType { + return &file_sync_live_proto_enumTypes[1] +} + +func (x DistributionKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DistributionKind.Descriptor instead. +func (DistributionKind) EnumDescriptor() ([]byte, []int) { + return file_sync_live_proto_rawDescGZIP(), []int{1} +} + type IceServer struct { state protoimpl.MessageState `protogen:"open.v1"` Urls []string `protobuf:"bytes,1,rep,name=urls,proto3" json:"urls,omitempty"` @@ -234,18 +286,156 @@ func (x *BackendInfo) GetPrimary() bool { return false } +type DistributorInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Kind DistributionKind `protobuf:"varint,1,opt,name=kind,proto3,enum=synclive.v1.DistributionKind" json:"kind,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Configured bool `protobuf:"varint,3,opt,name=configured,proto3" json:"configured,omitempty"` + Enabled bool `protobuf:"varint,4,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DistributorInfo) Reset() { + *x = DistributorInfo{} + mi := &file_sync_live_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DistributorInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DistributorInfo) ProtoMessage() {} + +func (x *DistributorInfo) ProtoReflect() protoreflect.Message { + mi := &file_sync_live_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DistributorInfo.ProtoReflect.Descriptor instead. +func (*DistributorInfo) Descriptor() ([]byte, []int) { + return file_sync_live_proto_rawDescGZIP(), []int{3} +} + +func (x *DistributorInfo) GetKind() DistributionKind { + if x != nil { + return x.Kind + } + return DistributionKind_DISTRIBUTION_KIND_UNSPECIFIED +} + +func (x *DistributorInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DistributorInfo) GetConfigured() bool { + if x != nil { + return x.Configured + } + return false +} + +func (x *DistributorInfo) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type DistributionTarget struct { + state protoimpl.MessageState `protogen:"open.v1"` + Kind DistributionKind `protobuf:"varint,1,opt,name=kind,proto3,enum=synclive.v1.DistributionKind" json:"kind,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` // forwarding | ready | error + UpdatedAt int64 `protobuf:"varint,4,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DistributionTarget) Reset() { + *x = DistributionTarget{} + mi := &file_sync_live_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DistributionTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DistributionTarget) ProtoMessage() {} + +func (x *DistributionTarget) ProtoReflect() protoreflect.Message { + mi := &file_sync_live_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DistributionTarget.ProtoReflect.Descriptor instead. +func (*DistributionTarget) Descriptor() ([]byte, []int) { + return file_sync_live_proto_rawDescGZIP(), []int{4} +} + +func (x *DistributionTarget) GetKind() DistributionKind { + if x != nil { + return x.Kind + } + return DistributionKind_DISTRIBUTION_KIND_UNSPECIFIED +} + +func (x *DistributionTarget) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *DistributionTarget) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *DistributionTarget) GetUpdatedAt() int64 { + if x != nil { + return x.UpdatedAt + } + return 0 +} + type GetConfigResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Backends []*BackendInfo `protobuf:"bytes,1,rep,name=backends,proto3" json:"backends,omitempty"` Candidate string `protobuf:"bytes,2,opt,name=candidate,proto3" json:"candidate,omitempty"` TokenRequired bool `protobuf:"varint,3,opt,name=token_required,json=tokenRequired,proto3" json:"token_required,omitempty"` + Trunk *BackendInfo `protobuf:"bytes,4,opt,name=trunk,proto3" json:"trunk,omitempty"` + Distributors []*DistributorInfo `protobuf:"bytes,5,rep,name=distributors,proto3" json:"distributors,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetConfigResponse) Reset() { *x = GetConfigResponse{} - mi := &file_sync_live_proto_msgTypes[3] + mi := &file_sync_live_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -257,7 +447,7 @@ func (x *GetConfigResponse) String() string { func (*GetConfigResponse) ProtoMessage() {} func (x *GetConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[3] + mi := &file_sync_live_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -270,7 +460,7 @@ func (x *GetConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetConfigResponse.ProtoReflect.Descriptor instead. func (*GetConfigResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{3} + return file_sync_live_proto_rawDescGZIP(), []int{5} } func (x *GetConfigResponse) GetBackends() []*BackendInfo { @@ -294,6 +484,20 @@ func (x *GetConfigResponse) GetTokenRequired() bool { return false } +func (x *GetConfigResponse) GetTrunk() *BackendInfo { + if x != nil { + return x.Trunk + } + return nil +} + +func (x *GetConfigResponse) GetDistributors() []*DistributorInfo { + if x != nil { + return x.Distributors + } + return nil +} + type StreamTarget struct { state protoimpl.MessageState `protogen:"open.v1"` Backend BackendKind `protobuf:"varint,1,opt,name=backend,proto3,enum=synclive.v1.BackendKind" json:"backend,omitempty"` @@ -308,7 +512,7 @@ type StreamTarget struct { func (x *StreamTarget) Reset() { *x = StreamTarget{} - mi := &file_sync_live_proto_msgTypes[4] + mi := &file_sync_live_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -320,7 +524,7 @@ func (x *StreamTarget) String() string { func (*StreamTarget) ProtoMessage() {} func (x *StreamTarget) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[4] + mi := &file_sync_live_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -333,7 +537,7 @@ func (x *StreamTarget) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamTarget.ProtoReflect.Descriptor instead. func (*StreamTarget) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{4} + return file_sync_live_proto_rawDescGZIP(), []int{6} } func (x *StreamTarget) GetBackend() BackendKind { @@ -382,13 +586,14 @@ type Room struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Targets []*StreamTarget `protobuf:"bytes,2,rep,name=targets,proto3" json:"targets,omitempty"` + Distributions []*DistributionTarget `protobuf:"bytes,3,rep,name=distributions,proto3" json:"distributions,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Room) Reset() { *x = Room{} - mi := &file_sync_live_proto_msgTypes[5] + mi := &file_sync_live_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -400,7 +605,7 @@ func (x *Room) String() string { func (*Room) ProtoMessage() {} func (x *Room) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[5] + mi := &file_sync_live_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -413,7 +618,7 @@ func (x *Room) ProtoReflect() protoreflect.Message { // Deprecated: Use Room.ProtoReflect.Descriptor instead. func (*Room) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{5} + return file_sync_live_proto_rawDescGZIP(), []int{7} } func (x *Room) GetName() string { @@ -430,6 +635,13 @@ func (x *Room) GetTargets() []*StreamTarget { return nil } +func (x *Room) GetDistributions() []*DistributionTarget { + if x != nil { + return x.Distributions + } + return nil +} + type ListRoomsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -438,7 +650,7 @@ type ListRoomsRequest struct { func (x *ListRoomsRequest) Reset() { *x = ListRoomsRequest{} - mi := &file_sync_live_proto_msgTypes[6] + mi := &file_sync_live_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -450,7 +662,7 @@ func (x *ListRoomsRequest) String() string { func (*ListRoomsRequest) ProtoMessage() {} func (x *ListRoomsRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[6] + mi := &file_sync_live_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -463,7 +675,7 @@ func (x *ListRoomsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomsRequest.ProtoReflect.Descriptor instead. func (*ListRoomsRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{6} + return file_sync_live_proto_rawDescGZIP(), []int{8} } type ListRoomsResponse struct { @@ -475,7 +687,7 @@ type ListRoomsResponse struct { func (x *ListRoomsResponse) Reset() { *x = ListRoomsResponse{} - mi := &file_sync_live_proto_msgTypes[7] + mi := &file_sync_live_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -487,7 +699,7 @@ func (x *ListRoomsResponse) String() string { func (*ListRoomsResponse) ProtoMessage() {} func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[7] + mi := &file_sync_live_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -500,7 +712,7 @@ func (x *ListRoomsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoomsResponse.ProtoReflect.Descriptor instead. func (*ListRoomsResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{7} + return file_sync_live_proto_rawDescGZIP(), []int{9} } func (x *ListRoomsResponse) GetRooms() []*Room { @@ -521,7 +733,7 @@ type PublishRequest struct { func (x *PublishRequest) Reset() { *x = PublishRequest{} - mi := &file_sync_live_proto_msgTypes[8] + mi := &file_sync_live_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -533,7 +745,7 @@ func (x *PublishRequest) String() string { func (*PublishRequest) ProtoMessage() {} func (x *PublishRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[8] + mi := &file_sync_live_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -546,7 +758,7 @@ func (x *PublishRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PublishRequest.ProtoReflect.Descriptor instead. func (*PublishRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{8} + return file_sync_live_proto_rawDescGZIP(), []int{10} } func (x *PublishRequest) GetRoom() string { @@ -583,7 +795,7 @@ type PublishResponse struct { func (x *PublishResponse) Reset() { *x = PublishResponse{} - mi := &file_sync_live_proto_msgTypes[9] + mi := &file_sync_live_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -595,7 +807,7 @@ func (x *PublishResponse) String() string { func (*PublishResponse) ProtoMessage() {} func (x *PublishResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[9] + mi := &file_sync_live_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -608,7 +820,7 @@ func (x *PublishResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PublishResponse.ProtoReflect.Descriptor instead. func (*PublishResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{9} + return file_sync_live_proto_rawDescGZIP(), []int{11} } func (x *PublishResponse) GetSessionId() string { @@ -657,7 +869,7 @@ type SubscribeRequest struct { func (x *SubscribeRequest) Reset() { *x = SubscribeRequest{} - mi := &file_sync_live_proto_msgTypes[10] + mi := &file_sync_live_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -669,7 +881,7 @@ func (x *SubscribeRequest) String() string { func (*SubscribeRequest) ProtoMessage() {} func (x *SubscribeRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[10] + mi := &file_sync_live_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -682,7 +894,7 @@ func (x *SubscribeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscribeRequest.ProtoReflect.Descriptor instead. func (*SubscribeRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{10} + return file_sync_live_proto_rawDescGZIP(), []int{12} } func (x *SubscribeRequest) GetRoom() string { @@ -718,7 +930,7 @@ type SubscribeResponse struct { func (x *SubscribeResponse) Reset() { *x = SubscribeResponse{} - mi := &file_sync_live_proto_msgTypes[11] + mi := &file_sync_live_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -730,7 +942,7 @@ func (x *SubscribeResponse) String() string { func (*SubscribeResponse) ProtoMessage() {} func (x *SubscribeResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[11] + mi := &file_sync_live_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -743,7 +955,7 @@ func (x *SubscribeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscribeResponse.ProtoReflect.Descriptor instead. func (*SubscribeResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{11} + return file_sync_live_proto_rawDescGZIP(), []int{13} } func (x *SubscribeResponse) GetSessionId() string { @@ -784,7 +996,7 @@ type StopStreamRequest struct { func (x *StopStreamRequest) Reset() { *x = StopStreamRequest{} - mi := &file_sync_live_proto_msgTypes[12] + mi := &file_sync_live_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -796,7 +1008,7 @@ func (x *StopStreamRequest) String() string { func (*StopStreamRequest) ProtoMessage() {} func (x *StopStreamRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[12] + mi := &file_sync_live_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -809,7 +1021,7 @@ func (x *StopStreamRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopStreamRequest.ProtoReflect.Descriptor instead. func (*StopStreamRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{12} + return file_sync_live_proto_rawDescGZIP(), []int{14} } func (x *StopStreamRequest) GetRoom() string { @@ -835,7 +1047,7 @@ type StopStreamResponse struct { func (x *StopStreamResponse) Reset() { *x = StopStreamResponse{} - mi := &file_sync_live_proto_msgTypes[13] + mi := &file_sync_live_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -847,7 +1059,7 @@ func (x *StopStreamResponse) String() string { func (*StopStreamResponse) ProtoMessage() {} func (x *StopStreamResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[13] + mi := &file_sync_live_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -860,7 +1072,7 @@ func (x *StopStreamResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopStreamResponse.ProtoReflect.Descriptor instead. func (*StopStreamResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{13} + return file_sync_live_proto_rawDescGZIP(), []int{15} } func (x *StopStreamResponse) GetOk() bool { @@ -879,7 +1091,7 @@ type WatchRoomRequest struct { func (x *WatchRoomRequest) Reset() { *x = WatchRoomRequest{} - mi := &file_sync_live_proto_msgTypes[14] + mi := &file_sync_live_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -891,7 +1103,7 @@ func (x *WatchRoomRequest) String() string { func (*WatchRoomRequest) ProtoMessage() {} func (x *WatchRoomRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[14] + mi := &file_sync_live_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -904,7 +1116,7 @@ func (x *WatchRoomRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchRoomRequest.ProtoReflect.Descriptor instead. func (*WatchRoomRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{14} + return file_sync_live_proto_rawDescGZIP(), []int{16} } func (x *WatchRoomRequest) GetRoom() string { @@ -918,13 +1130,14 @@ type RoomEvent struct { state protoimpl.MessageState `protogen:"open.v1"` Room string `protobuf:"bytes,1,opt,name=room,proto3" json:"room,omitempty"` Targets []*StreamTarget `protobuf:"bytes,2,rep,name=targets,proto3" json:"targets,omitempty"` + Distributions []*DistributionTarget `protobuf:"bytes,3,rep,name=distributions,proto3" json:"distributions,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RoomEvent) Reset() { *x = RoomEvent{} - mi := &file_sync_live_proto_msgTypes[15] + mi := &file_sync_live_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -936,7 +1149,7 @@ func (x *RoomEvent) String() string { func (*RoomEvent) ProtoMessage() {} func (x *RoomEvent) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[15] + mi := &file_sync_live_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -949,7 +1162,7 @@ func (x *RoomEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomEvent.ProtoReflect.Descriptor instead. func (*RoomEvent) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{15} + return file_sync_live_proto_rawDescGZIP(), []int{17} } func (x *RoomEvent) GetRoom() string { @@ -966,6 +1179,13 @@ func (x *RoomEvent) GetTargets() []*StreamTarget { return nil } +func (x *RoomEvent) GetDistributions() []*DistributionTarget { + if x != nil { + return x.Distributions + } + return nil +} + // ===== Auth & RBAC (Casbin) ===== type UserInfo struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -978,7 +1198,7 @@ type UserInfo struct { func (x *UserInfo) Reset() { *x = UserInfo{} - mi := &file_sync_live_proto_msgTypes[16] + mi := &file_sync_live_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -990,7 +1210,7 @@ func (x *UserInfo) String() string { func (*UserInfo) ProtoMessage() {} func (x *UserInfo) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[16] + mi := &file_sync_live_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1003,7 +1223,7 @@ func (x *UserInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use UserInfo.ProtoReflect.Descriptor instead. func (*UserInfo) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{16} + return file_sync_live_proto_rawDescGZIP(), []int{18} } func (x *UserInfo) GetUsername() string { @@ -1037,7 +1257,7 @@ type LoginRequest struct { func (x *LoginRequest) Reset() { *x = LoginRequest{} - mi := &file_sync_live_proto_msgTypes[17] + mi := &file_sync_live_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1049,7 +1269,7 @@ func (x *LoginRequest) String() string { func (*LoginRequest) ProtoMessage() {} func (x *LoginRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[17] + mi := &file_sync_live_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1062,7 +1282,7 @@ func (x *LoginRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. func (*LoginRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{17} + return file_sync_live_proto_rawDescGZIP(), []int{19} } func (x *LoginRequest) GetUsername() string { @@ -1090,7 +1310,7 @@ type LoginResponse struct { func (x *LoginResponse) Reset() { *x = LoginResponse{} - mi := &file_sync_live_proto_msgTypes[18] + mi := &file_sync_live_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1102,7 +1322,7 @@ func (x *LoginResponse) String() string { func (*LoginResponse) ProtoMessage() {} func (x *LoginResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[18] + mi := &file_sync_live_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1115,7 +1335,7 @@ func (x *LoginResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. func (*LoginResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{18} + return file_sync_live_proto_rawDescGZIP(), []int{20} } func (x *LoginResponse) GetToken() string { @@ -1150,7 +1370,7 @@ type RegisterRequest struct { func (x *RegisterRequest) Reset() { *x = RegisterRequest{} - mi := &file_sync_live_proto_msgTypes[19] + mi := &file_sync_live_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1162,7 +1382,7 @@ func (x *RegisterRequest) String() string { func (*RegisterRequest) ProtoMessage() {} func (x *RegisterRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[19] + mi := &file_sync_live_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1175,7 +1395,7 @@ func (x *RegisterRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterRequest.ProtoReflect.Descriptor instead. func (*RegisterRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{19} + return file_sync_live_proto_rawDescGZIP(), []int{21} } func (x *RegisterRequest) GetUsername() string { @@ -1210,7 +1430,7 @@ type RegisterResponse struct { func (x *RegisterResponse) Reset() { *x = RegisterResponse{} - mi := &file_sync_live_proto_msgTypes[20] + mi := &file_sync_live_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1222,7 +1442,7 @@ func (x *RegisterResponse) String() string { func (*RegisterResponse) ProtoMessage() {} func (x *RegisterResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[20] + mi := &file_sync_live_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1235,7 +1455,7 @@ func (x *RegisterResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterResponse.ProtoReflect.Descriptor instead. func (*RegisterResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{20} + return file_sync_live_proto_rawDescGZIP(), []int{22} } func (x *RegisterResponse) GetToken() string { @@ -1267,7 +1487,7 @@ type GetMeRequest struct { func (x *GetMeRequest) Reset() { *x = GetMeRequest{} - mi := &file_sync_live_proto_msgTypes[21] + mi := &file_sync_live_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1279,7 +1499,7 @@ func (x *GetMeRequest) String() string { func (*GetMeRequest) ProtoMessage() {} func (x *GetMeRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[21] + mi := &file_sync_live_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1292,7 +1512,7 @@ func (x *GetMeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMeRequest.ProtoReflect.Descriptor instead. func (*GetMeRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{21} + return file_sync_live_proto_rawDescGZIP(), []int{23} } type GetMeResponse struct { @@ -1306,7 +1526,7 @@ type GetMeResponse struct { func (x *GetMeResponse) Reset() { *x = GetMeResponse{} - mi := &file_sync_live_proto_msgTypes[22] + mi := &file_sync_live_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1318,7 +1538,7 @@ func (x *GetMeResponse) String() string { func (*GetMeResponse) ProtoMessage() {} func (x *GetMeResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[22] + mi := &file_sync_live_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1331,7 +1551,7 @@ func (x *GetMeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMeResponse.ProtoReflect.Descriptor instead. func (*GetMeResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{22} + return file_sync_live_proto_rawDescGZIP(), []int{24} } func (x *GetMeResponse) GetUser() *UserInfo { @@ -1363,7 +1583,7 @@ type ListUsersRequest struct { func (x *ListUsersRequest) Reset() { *x = ListUsersRequest{} - mi := &file_sync_live_proto_msgTypes[23] + mi := &file_sync_live_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1375,7 +1595,7 @@ func (x *ListUsersRequest) String() string { func (*ListUsersRequest) ProtoMessage() {} func (x *ListUsersRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[23] + mi := &file_sync_live_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1388,7 +1608,7 @@ func (x *ListUsersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUsersRequest.ProtoReflect.Descriptor instead. func (*ListUsersRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{23} + return file_sync_live_proto_rawDescGZIP(), []int{25} } type ListUsersResponse struct { @@ -1400,7 +1620,7 @@ type ListUsersResponse struct { func (x *ListUsersResponse) Reset() { *x = ListUsersResponse{} - mi := &file_sync_live_proto_msgTypes[24] + mi := &file_sync_live_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1412,7 +1632,7 @@ func (x *ListUsersResponse) String() string { func (*ListUsersResponse) ProtoMessage() {} func (x *ListUsersResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[24] + mi := &file_sync_live_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1425,7 +1645,7 @@ func (x *ListUsersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUsersResponse.ProtoReflect.Descriptor instead. func (*ListUsersResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{24} + return file_sync_live_proto_rawDescGZIP(), []int{26} } func (x *ListUsersResponse) GetUsers() []*UserInfo { @@ -1445,7 +1665,7 @@ type UpdateUserRoleRequest struct { func (x *UpdateUserRoleRequest) Reset() { *x = UpdateUserRoleRequest{} - mi := &file_sync_live_proto_msgTypes[25] + mi := &file_sync_live_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1457,7 +1677,7 @@ func (x *UpdateUserRoleRequest) String() string { func (*UpdateUserRoleRequest) ProtoMessage() {} func (x *UpdateUserRoleRequest) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[25] + mi := &file_sync_live_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1470,7 +1690,7 @@ func (x *UpdateUserRoleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserRoleRequest.ProtoReflect.Descriptor instead. func (*UpdateUserRoleRequest) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{25} + return file_sync_live_proto_rawDescGZIP(), []int{27} } func (x *UpdateUserRoleRequest) GetUsername() string { @@ -1497,7 +1717,7 @@ type UpdateUserRoleResponse struct { func (x *UpdateUserRoleResponse) Reset() { *x = UpdateUserRoleResponse{} - mi := &file_sync_live_proto_msgTypes[26] + mi := &file_sync_live_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1509,7 +1729,7 @@ func (x *UpdateUserRoleResponse) String() string { func (*UpdateUserRoleResponse) ProtoMessage() {} func (x *UpdateUserRoleResponse) ProtoReflect() protoreflect.Message { - mi := &file_sync_live_proto_msgTypes[26] + mi := &file_sync_live_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1522,7 +1742,7 @@ func (x *UpdateUserRoleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateUserRoleResponse.ProtoReflect.Descriptor instead. func (*UpdateUserRoleResponse) Descriptor() ([]byte, []int) { - return file_sync_live_proto_rawDescGZIP(), []int{26} + return file_sync_live_proto_rawDescGZIP(), []int{28} } func (x *UpdateUserRoleResponse) GetOk() bool { @@ -1557,11 +1777,26 @@ const file_sync_live_proto_rawDesc = "" + "\n" + "configured\x18\x03 \x01(\bR\n" + "configured\x12\x18\n" + - "\aprimary\x18\x04 \x01(\bR\aprimary\"\x8e\x01\n" + + "\aprimary\x18\x04 \x01(\bR\aprimary\"\x92\x01\n" + + "\x0fDistributorInfo\x121\n" + + "\x04kind\x18\x01 \x01(\x0e2\x1d.synclive.v1.DistributionKindR\x04kind\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1e\n" + + "\n" + + "configured\x18\x03 \x01(\bR\n" + + "configured\x12\x18\n" + + "\aenabled\x18\x04 \x01(\bR\aenabled\"\x90\x01\n" + + "\x12DistributionTarget\x121\n" + + "\x04kind\x18\x01 \x01(\x0e2\x1d.synclive.v1.DistributionKindR\x04kind\x12\x10\n" + + "\x03url\x18\x02 \x01(\tR\x03url\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "updated_at\x18\x04 \x01(\x03R\tupdatedAt\"\x80\x02\n" + "\x11GetConfigResponse\x124\n" + "\bbackends\x18\x01 \x03(\v2\x18.synclive.v1.BackendInfoR\bbackends\x12\x1c\n" + "\tcandidate\x18\x02 \x01(\tR\tcandidate\x12%\n" + - "\x0etoken_required\x18\x03 \x01(\bR\rtokenRequired\"\xd3\x01\n" + + "\x0etoken_required\x18\x03 \x01(\bR\rtokenRequired\x12.\n" + + "\x05trunk\x18\x04 \x01(\v2\x18.synclive.v1.BackendInfoR\x05trunk\x12@\n" + + "\fdistributors\x18\x05 \x03(\v2\x1c.synclive.v1.DistributorInfoR\fdistributors\"\xd3\x01\n" + "\fStreamTarget\x122\n" + "\abackend\x18\x01 \x01(\x0e2\x18.synclive.v1.BackendKindR\abackend\x12\x1d\n" + "\n" + @@ -1569,10 +1804,11 @@ const file_sync_live_proto_rawDesc = "" + "\x06stream\x18\x03 \x01(\tR\x06stream\x12#\n" + "\rpublish_token\x18\x04 \x01(\tR\fpublishToken\x12\x10\n" + "\x03url\x18\x05 \x01(\tR\x03url\x12!\n" + - "\fpublished_at\x18\x06 \x01(\x03R\vpublishedAt\"O\n" + + "\fpublished_at\x18\x06 \x01(\x03R\vpublishedAt\"\x96\x01\n" + "\x04Room\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x123\n" + - "\atargets\x18\x02 \x03(\v2\x19.synclive.v1.StreamTargetR\atargets\"\x12\n" + + "\atargets\x18\x02 \x03(\v2\x19.synclive.v1.StreamTargetR\atargets\x12E\n" + + "\rdistributions\x18\x03 \x03(\v2\x1f.synclive.v1.DistributionTargetR\rdistributions\"\x12\n" + "\x10ListRoomsRequest\"<\n" + "\x11ListRoomsResponse\x12'\n" + "\x05rooms\x18\x01 \x03(\v2\x11.synclive.v1.RoomR\x05rooms\"t\n" + @@ -1605,10 +1841,11 @@ const file_sync_live_proto_rawDesc = "" + "\x12StopStreamResponse\x12\x0e\n" + "\x02ok\x18\x01 \x01(\bR\x02ok\"&\n" + "\x10WatchRoomRequest\x12\x12\n" + - "\x04room\x18\x01 \x01(\tR\x04room\"T\n" + + "\x04room\x18\x01 \x01(\tR\x04room\"\x9b\x01\n" + "\tRoomEvent\x12\x12\n" + "\x04room\x18\x01 \x01(\tR\x04room\x123\n" + - "\atargets\x18\x02 \x03(\v2\x19.synclive.v1.StreamTargetR\atargets\"Y\n" + + "\atargets\x18\x02 \x03(\v2\x19.synclive.v1.StreamTargetR\atargets\x12E\n" + + "\rdistributions\x18\x03 \x03(\v2\x1f.synclive.v1.DistributionTargetR\rdistributions\"Y\n" + "\bUserInfo\x12\x1a\n" + "\busername\x18\x01 \x01(\tR\busername\x12\x12\n" + "\x04role\x18\x02 \x01(\tR\x04role\x12\x1d\n" + @@ -1649,7 +1886,12 @@ const file_sync_live_proto_rawDesc = "" + "\vBackendKind\x12\x1c\n" + "\x18BACKEND_KIND_UNSPECIFIED\x10\x00\x12\x1b\n" + "\x17BACKEND_KIND_CLOUDFLARE\x10\x01\x12\x14\n" + - "\x10BACKEND_KIND_SRS\x10\x022\xb9\x06\n" + + "\x10BACKEND_KIND_SRS\x10\x02*\x8d\x01\n" + + "\x10DistributionKind\x12!\n" + + "\x1dDISTRIBUTION_KIND_UNSPECIFIED\x10\x00\x12\x1d\n" + + "\x19DISTRIBUTION_KIND_SRS_HLS\x10\x01\x12\x1c\n" + + "\x18DISTRIBUTION_KIND_CF_SFU\x10\x02\x12\x19\n" + + "\x15DISTRIBUTION_KIND_CDN\x10\x032\xb9\x06\n" + "\bSyncLive\x12J\n" + "\tGetConfig\x12\x1d.synclive.v1.GetConfigRequest\x1a\x1e.synclive.v1.GetConfigResponse\x12J\n" + "\tListRooms\x12\x1d.synclive.v1.ListRoomsRequest\x1a\x1e.synclive.v1.ListRoomsResponse\x12D\n" + @@ -1676,83 +1918,92 @@ func file_sync_live_proto_rawDescGZIP() []byte { return file_sync_live_proto_rawDescData } -var file_sync_live_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_sync_live_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_sync_live_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_sync_live_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_sync_live_proto_goTypes = []any{ (BackendKind)(0), // 0: synclive.v1.BackendKind - (*IceServer)(nil), // 1: synclive.v1.IceServer - (*GetConfigRequest)(nil), // 2: synclive.v1.GetConfigRequest - (*BackendInfo)(nil), // 3: synclive.v1.BackendInfo - (*GetConfigResponse)(nil), // 4: synclive.v1.GetConfigResponse - (*StreamTarget)(nil), // 5: synclive.v1.StreamTarget - (*Room)(nil), // 6: synclive.v1.Room - (*ListRoomsRequest)(nil), // 7: synclive.v1.ListRoomsRequest - (*ListRoomsResponse)(nil), // 8: synclive.v1.ListRoomsResponse - (*PublishRequest)(nil), // 9: synclive.v1.PublishRequest - (*PublishResponse)(nil), // 10: synclive.v1.PublishResponse - (*SubscribeRequest)(nil), // 11: synclive.v1.SubscribeRequest - (*SubscribeResponse)(nil), // 12: synclive.v1.SubscribeResponse - (*StopStreamRequest)(nil), // 13: synclive.v1.StopStreamRequest - (*StopStreamResponse)(nil), // 14: synclive.v1.StopStreamResponse - (*WatchRoomRequest)(nil), // 15: synclive.v1.WatchRoomRequest - (*RoomEvent)(nil), // 16: synclive.v1.RoomEvent - (*UserInfo)(nil), // 17: synclive.v1.UserInfo - (*LoginRequest)(nil), // 18: synclive.v1.LoginRequest - (*LoginResponse)(nil), // 19: synclive.v1.LoginResponse - (*RegisterRequest)(nil), // 20: synclive.v1.RegisterRequest - (*RegisterResponse)(nil), // 21: synclive.v1.RegisterResponse - (*GetMeRequest)(nil), // 22: synclive.v1.GetMeRequest - (*GetMeResponse)(nil), // 23: synclive.v1.GetMeResponse - (*ListUsersRequest)(nil), // 24: synclive.v1.ListUsersRequest - (*ListUsersResponse)(nil), // 25: synclive.v1.ListUsersResponse - (*UpdateUserRoleRequest)(nil), // 26: synclive.v1.UpdateUserRoleRequest - (*UpdateUserRoleResponse)(nil), // 27: synclive.v1.UpdateUserRoleResponse + (DistributionKind)(0), // 1: synclive.v1.DistributionKind + (*IceServer)(nil), // 2: synclive.v1.IceServer + (*GetConfigRequest)(nil), // 3: synclive.v1.GetConfigRequest + (*BackendInfo)(nil), // 4: synclive.v1.BackendInfo + (*DistributorInfo)(nil), // 5: synclive.v1.DistributorInfo + (*DistributionTarget)(nil), // 6: synclive.v1.DistributionTarget + (*GetConfigResponse)(nil), // 7: synclive.v1.GetConfigResponse + (*StreamTarget)(nil), // 8: synclive.v1.StreamTarget + (*Room)(nil), // 9: synclive.v1.Room + (*ListRoomsRequest)(nil), // 10: synclive.v1.ListRoomsRequest + (*ListRoomsResponse)(nil), // 11: synclive.v1.ListRoomsResponse + (*PublishRequest)(nil), // 12: synclive.v1.PublishRequest + (*PublishResponse)(nil), // 13: synclive.v1.PublishResponse + (*SubscribeRequest)(nil), // 14: synclive.v1.SubscribeRequest + (*SubscribeResponse)(nil), // 15: synclive.v1.SubscribeResponse + (*StopStreamRequest)(nil), // 16: synclive.v1.StopStreamRequest + (*StopStreamResponse)(nil), // 17: synclive.v1.StopStreamResponse + (*WatchRoomRequest)(nil), // 18: synclive.v1.WatchRoomRequest + (*RoomEvent)(nil), // 19: synclive.v1.RoomEvent + (*UserInfo)(nil), // 20: synclive.v1.UserInfo + (*LoginRequest)(nil), // 21: synclive.v1.LoginRequest + (*LoginResponse)(nil), // 22: synclive.v1.LoginResponse + (*RegisterRequest)(nil), // 23: synclive.v1.RegisterRequest + (*RegisterResponse)(nil), // 24: synclive.v1.RegisterResponse + (*GetMeRequest)(nil), // 25: synclive.v1.GetMeRequest + (*GetMeResponse)(nil), // 26: synclive.v1.GetMeResponse + (*ListUsersRequest)(nil), // 27: synclive.v1.ListUsersRequest + (*ListUsersResponse)(nil), // 28: synclive.v1.ListUsersResponse + (*UpdateUserRoleRequest)(nil), // 29: synclive.v1.UpdateUserRoleRequest + (*UpdateUserRoleResponse)(nil), // 30: synclive.v1.UpdateUserRoleResponse } var file_sync_live_proto_depIdxs = []int32{ 0, // 0: synclive.v1.BackendInfo.kind:type_name -> synclive.v1.BackendKind - 3, // 1: synclive.v1.GetConfigResponse.backends:type_name -> synclive.v1.BackendInfo - 0, // 2: synclive.v1.StreamTarget.backend:type_name -> synclive.v1.BackendKind - 5, // 3: synclive.v1.Room.targets:type_name -> synclive.v1.StreamTarget - 6, // 4: synclive.v1.ListRoomsResponse.rooms:type_name -> synclive.v1.Room - 0, // 5: synclive.v1.PublishRequest.backend:type_name -> synclive.v1.BackendKind - 1, // 6: synclive.v1.PublishResponse.ice_servers:type_name -> synclive.v1.IceServer - 5, // 7: synclive.v1.PublishResponse.target:type_name -> synclive.v1.StreamTarget - 0, // 8: synclive.v1.SubscribeRequest.backend:type_name -> synclive.v1.BackendKind - 1, // 9: synclive.v1.SubscribeResponse.ice_servers:type_name -> synclive.v1.IceServer - 0, // 10: synclive.v1.StopStreamRequest.backend:type_name -> synclive.v1.BackendKind - 5, // 11: synclive.v1.RoomEvent.targets:type_name -> synclive.v1.StreamTarget - 17, // 12: synclive.v1.LoginResponse.user:type_name -> synclive.v1.UserInfo - 17, // 13: synclive.v1.RegisterResponse.user:type_name -> synclive.v1.UserInfo - 17, // 14: synclive.v1.GetMeResponse.user:type_name -> synclive.v1.UserInfo - 17, // 15: synclive.v1.ListUsersResponse.users:type_name -> synclive.v1.UserInfo - 17, // 16: synclive.v1.UpdateUserRoleResponse.user:type_name -> synclive.v1.UserInfo - 2, // 17: synclive.v1.SyncLive.GetConfig:input_type -> synclive.v1.GetConfigRequest - 7, // 18: synclive.v1.SyncLive.ListRooms:input_type -> synclive.v1.ListRoomsRequest - 9, // 19: synclive.v1.SyncLive.Publish:input_type -> synclive.v1.PublishRequest - 11, // 20: synclive.v1.SyncLive.Subscribe:input_type -> synclive.v1.SubscribeRequest - 13, // 21: synclive.v1.SyncLive.StopStream:input_type -> synclive.v1.StopStreamRequest - 15, // 22: synclive.v1.SyncLive.WatchRoom:input_type -> synclive.v1.WatchRoomRequest - 18, // 23: synclive.v1.SyncLive.Login:input_type -> synclive.v1.LoginRequest - 20, // 24: synclive.v1.SyncLive.Register:input_type -> synclive.v1.RegisterRequest - 22, // 25: synclive.v1.SyncLive.GetMe:input_type -> synclive.v1.GetMeRequest - 24, // 26: synclive.v1.SyncLive.ListUsers:input_type -> synclive.v1.ListUsersRequest - 26, // 27: synclive.v1.SyncLive.UpdateUserRole:input_type -> synclive.v1.UpdateUserRoleRequest - 4, // 28: synclive.v1.SyncLive.GetConfig:output_type -> synclive.v1.GetConfigResponse - 8, // 29: synclive.v1.SyncLive.ListRooms:output_type -> synclive.v1.ListRoomsResponse - 10, // 30: synclive.v1.SyncLive.Publish:output_type -> synclive.v1.PublishResponse - 12, // 31: synclive.v1.SyncLive.Subscribe:output_type -> synclive.v1.SubscribeResponse - 14, // 32: synclive.v1.SyncLive.StopStream:output_type -> synclive.v1.StopStreamResponse - 16, // 33: synclive.v1.SyncLive.WatchRoom:output_type -> synclive.v1.RoomEvent - 19, // 34: synclive.v1.SyncLive.Login:output_type -> synclive.v1.LoginResponse - 21, // 35: synclive.v1.SyncLive.Register:output_type -> synclive.v1.RegisterResponse - 23, // 36: synclive.v1.SyncLive.GetMe:output_type -> synclive.v1.GetMeResponse - 25, // 37: synclive.v1.SyncLive.ListUsers:output_type -> synclive.v1.ListUsersResponse - 27, // 38: synclive.v1.SyncLive.UpdateUserRole:output_type -> synclive.v1.UpdateUserRoleResponse - 28, // [28:39] is the sub-list for method output_type - 17, // [17:28] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 1, // 1: synclive.v1.DistributorInfo.kind:type_name -> synclive.v1.DistributionKind + 1, // 2: synclive.v1.DistributionTarget.kind:type_name -> synclive.v1.DistributionKind + 4, // 3: synclive.v1.GetConfigResponse.backends:type_name -> synclive.v1.BackendInfo + 4, // 4: synclive.v1.GetConfigResponse.trunk:type_name -> synclive.v1.BackendInfo + 5, // 5: synclive.v1.GetConfigResponse.distributors:type_name -> synclive.v1.DistributorInfo + 0, // 6: synclive.v1.StreamTarget.backend:type_name -> synclive.v1.BackendKind + 8, // 7: synclive.v1.Room.targets:type_name -> synclive.v1.StreamTarget + 6, // 8: synclive.v1.Room.distributions:type_name -> synclive.v1.DistributionTarget + 9, // 9: synclive.v1.ListRoomsResponse.rooms:type_name -> synclive.v1.Room + 0, // 10: synclive.v1.PublishRequest.backend:type_name -> synclive.v1.BackendKind + 2, // 11: synclive.v1.PublishResponse.ice_servers:type_name -> synclive.v1.IceServer + 8, // 12: synclive.v1.PublishResponse.target:type_name -> synclive.v1.StreamTarget + 0, // 13: synclive.v1.SubscribeRequest.backend:type_name -> synclive.v1.BackendKind + 2, // 14: synclive.v1.SubscribeResponse.ice_servers:type_name -> synclive.v1.IceServer + 0, // 15: synclive.v1.StopStreamRequest.backend:type_name -> synclive.v1.BackendKind + 8, // 16: synclive.v1.RoomEvent.targets:type_name -> synclive.v1.StreamTarget + 6, // 17: synclive.v1.RoomEvent.distributions:type_name -> synclive.v1.DistributionTarget + 20, // 18: synclive.v1.LoginResponse.user:type_name -> synclive.v1.UserInfo + 20, // 19: synclive.v1.RegisterResponse.user:type_name -> synclive.v1.UserInfo + 20, // 20: synclive.v1.GetMeResponse.user:type_name -> synclive.v1.UserInfo + 20, // 21: synclive.v1.ListUsersResponse.users:type_name -> synclive.v1.UserInfo + 20, // 22: synclive.v1.UpdateUserRoleResponse.user:type_name -> synclive.v1.UserInfo + 3, // 23: synclive.v1.SyncLive.GetConfig:input_type -> synclive.v1.GetConfigRequest + 10, // 24: synclive.v1.SyncLive.ListRooms:input_type -> synclive.v1.ListRoomsRequest + 12, // 25: synclive.v1.SyncLive.Publish:input_type -> synclive.v1.PublishRequest + 14, // 26: synclive.v1.SyncLive.Subscribe:input_type -> synclive.v1.SubscribeRequest + 16, // 27: synclive.v1.SyncLive.StopStream:input_type -> synclive.v1.StopStreamRequest + 18, // 28: synclive.v1.SyncLive.WatchRoom:input_type -> synclive.v1.WatchRoomRequest + 21, // 29: synclive.v1.SyncLive.Login:input_type -> synclive.v1.LoginRequest + 23, // 30: synclive.v1.SyncLive.Register:input_type -> synclive.v1.RegisterRequest + 25, // 31: synclive.v1.SyncLive.GetMe:input_type -> synclive.v1.GetMeRequest + 27, // 32: synclive.v1.SyncLive.ListUsers:input_type -> synclive.v1.ListUsersRequest + 29, // 33: synclive.v1.SyncLive.UpdateUserRole:input_type -> synclive.v1.UpdateUserRoleRequest + 7, // 34: synclive.v1.SyncLive.GetConfig:output_type -> synclive.v1.GetConfigResponse + 11, // 35: synclive.v1.SyncLive.ListRooms:output_type -> synclive.v1.ListRoomsResponse + 13, // 36: synclive.v1.SyncLive.Publish:output_type -> synclive.v1.PublishResponse + 15, // 37: synclive.v1.SyncLive.Subscribe:output_type -> synclive.v1.SubscribeResponse + 17, // 38: synclive.v1.SyncLive.StopStream:output_type -> synclive.v1.StopStreamResponse + 19, // 39: synclive.v1.SyncLive.WatchRoom:output_type -> synclive.v1.RoomEvent + 22, // 40: synclive.v1.SyncLive.Login:output_type -> synclive.v1.LoginResponse + 24, // 41: synclive.v1.SyncLive.Register:output_type -> synclive.v1.RegisterResponse + 26, // 42: synclive.v1.SyncLive.GetMe:output_type -> synclive.v1.GetMeResponse + 28, // 43: synclive.v1.SyncLive.ListUsers:output_type -> synclive.v1.ListUsersResponse + 30, // 44: synclive.v1.SyncLive.UpdateUserRole:output_type -> synclive.v1.UpdateUserRoleResponse + 34, // [34:45] is the sub-list for method output_type + 23, // [23:34] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_sync_live_proto_init() } @@ -1765,8 +2016,8 @@ func file_sync_live_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_sync_live_proto_rawDesc), len(file_sync_live_proto_rawDesc)), - NumEnums: 1, - NumMessages: 27, + NumEnums: 2, + NumMessages: 29, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/config/config.go b/internal/config/config.go index dee34cd..46e4522 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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 := "?" diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..b552719 --- /dev/null +++ b/internal/config/config_test.go @@ -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") + } +} diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index da47a81..2ca05ed 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -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 {