mirror of
https://github.com/langhuihui/monibuca.git
synced 2026-04-22 20:47:04 +08:00
feat: casecade plugin support multi server
This commit is contained in:
@@ -1057,6 +1057,12 @@ func (s *Server) GetRecordList(ctx context.Context, req *pb.ReqRecordList) (resp
|
||||
EndTime: timestamppb.New(recordFile.EndTime),
|
||||
FilePath: recordFile.FilePath,
|
||||
StreamPath: recordFile.StreamPath,
|
||||
Filename: recordFile.Filename,
|
||||
Type: recordFile.Type,
|
||||
Duration: recordFile.Duration,
|
||||
AudioCodec: recordFile.AudioCodec,
|
||||
VideoCodec: recordFile.VideoCodec,
|
||||
CreatedAt: timestamppb.New(recordFile.CreatedAt),
|
||||
})
|
||||
}
|
||||
return
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v6.31.1
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// source: auth.proto
|
||||
|
||||
package pb
|
||||
|
||||
+1
-9
@@ -43,9 +43,6 @@ func request_Auth_Login_0(ctx context.Context, marshaler runtime.Marshaler, clie
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Login(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
@@ -70,9 +67,6 @@ func request_Auth_Logout_0(ctx context.Context, marshaler runtime.Marshaler, cli
|
||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Logout(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
}
|
||||
@@ -96,9 +90,7 @@ func request_Auth_GetUserInfo_0(ctx context.Context, marshaler runtime.Marshaler
|
||||
protoReq UserInfoRequest
|
||||
metadata runtime.ServerMetadata
|
||||
)
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
io.Copy(io.Discard, req.Body)
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v6.31.1
|
||||
// - protoc v5.29.3
|
||||
// source: auth.proto
|
||||
|
||||
package pb
|
||||
|
||||
+183
-123
@@ -4449,6 +4449,12 @@ type RecordFile struct {
|
||||
StreamPath string `protobuf:"bytes,3,opt,name=streamPath,proto3" json:"streamPath,omitempty"`
|
||||
StartTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=startTime,proto3" json:"startTime,omitempty"`
|
||||
EndTime *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=endTime,proto3" json:"endTime,omitempty"`
|
||||
Filename string `protobuf:"bytes,6,opt,name=filename,proto3" json:"filename,omitempty"`
|
||||
Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Duration uint32 `protobuf:"varint,8,opt,name=duration,proto3" json:"duration,omitempty"`
|
||||
AudioCodec string `protobuf:"bytes,9,opt,name=audioCodec,proto3" json:"audioCodec,omitempty"`
|
||||
VideoCodec string `protobuf:"bytes,10,opt,name=videoCodec,proto3" json:"videoCodec,omitempty"`
|
||||
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=createdAt,proto3" json:"createdAt,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -4518,6 +4524,48 @@ func (x *RecordFile) GetEndTime() *timestamppb.Timestamp {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RecordFile) GetFilename() string {
|
||||
if x != nil {
|
||||
return x.Filename
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RecordFile) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RecordFile) GetDuration() uint32 {
|
||||
if x != nil {
|
||||
return x.Duration
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RecordFile) GetAudioCodec() string {
|
||||
if x != nil {
|
||||
return x.AudioCodec
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RecordFile) GetVideoCodec() string {
|
||||
if x != nil {
|
||||
return x.VideoCodec
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RecordFile) GetCreatedAt() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreatedAt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type EventRecordFile struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
@@ -6305,7 +6353,7 @@ const file_global_proto_rawDesc = "" +
|
||||
"\x04type\x18\a \x01(\tR\x04type\x12\x1e\n" +
|
||||
"\n" +
|
||||
"eventLevel\x18\b \x01(\tR\n" +
|
||||
"eventLevel\"\xc8\x01\n" +
|
||||
"eventLevel\"\x8e\x03\n" +
|
||||
"\n" +
|
||||
"RecordFile\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\rR\x02id\x12\x1a\n" +
|
||||
@@ -6314,7 +6362,18 @@ const file_global_proto_rawDesc = "" +
|
||||
"streamPath\x18\x03 \x01(\tR\n" +
|
||||
"streamPath\x128\n" +
|
||||
"\tstartTime\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tstartTime\x124\n" +
|
||||
"\aendTime\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\aendTime\"\xc3\x02\n" +
|
||||
"\aendTime\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\aendTime\x12\x1a\n" +
|
||||
"\bfilename\x18\x06 \x01(\tR\bfilename\x12\x12\n" +
|
||||
"\x04type\x18\a \x01(\tR\x04type\x12\x1a\n" +
|
||||
"\bduration\x18\b \x01(\rR\bduration\x12\x1e\n" +
|
||||
"\n" +
|
||||
"audioCodec\x18\t \x01(\tR\n" +
|
||||
"audioCodec\x12\x1e\n" +
|
||||
"\n" +
|
||||
"videoCodec\x18\n" +
|
||||
" \x01(\tR\n" +
|
||||
"videoCodec\x128\n" +
|
||||
"\tcreatedAt\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\"\xc3\x02\n" +
|
||||
"\x0fEventRecordFile\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\rR\x02id\x12\x1a\n" +
|
||||
"\bfilePath\x18\x02 \x01(\tR\bfilePath\x12\x1e\n" +
|
||||
@@ -6682,127 +6741,128 @@ var file_global_proto_depIdxs = []int32{
|
||||
57, // 53: global.TransformListResponse.data:type_name -> global.Transform
|
||||
83, // 54: global.RecordFile.startTime:type_name -> google.protobuf.Timestamp
|
||||
83, // 55: global.RecordFile.endTime:type_name -> google.protobuf.Timestamp
|
||||
83, // 56: global.EventRecordFile.startTime:type_name -> google.protobuf.Timestamp
|
||||
83, // 57: global.EventRecordFile.endTime:type_name -> google.protobuf.Timestamp
|
||||
60, // 58: global.RecordResponseList.data:type_name -> global.RecordFile
|
||||
61, // 59: global.EventRecordResponseList.data:type_name -> global.EventRecordFile
|
||||
83, // 60: global.Catalog.startTime:type_name -> google.protobuf.Timestamp
|
||||
83, // 61: global.Catalog.endTime:type_name -> google.protobuf.Timestamp
|
||||
64, // 62: global.ResponseCatalog.data:type_name -> global.Catalog
|
||||
60, // 63: global.ResponseDelete.data:type_name -> global.RecordFile
|
||||
83, // 64: global.AlarmInfo.createdAt:type_name -> google.protobuf.Timestamp
|
||||
83, // 65: global.AlarmInfo.updatedAt:type_name -> google.protobuf.Timestamp
|
||||
69, // 66: global.AlarmListResponse.data:type_name -> global.AlarmInfo
|
||||
83, // 67: global.Step.startedAt:type_name -> google.protobuf.Timestamp
|
||||
83, // 68: global.Step.completedAt:type_name -> google.protobuf.Timestamp
|
||||
72, // 69: global.SubscriptionProgressData.steps:type_name -> global.Step
|
||||
73, // 70: global.SubscriptionProgressResponse.data:type_name -> global.SubscriptionProgressData
|
||||
84, // 71: global.GlobalPullRequest.delayCloseTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 72: global.GlobalPullRequest.publishTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 73: global.GlobalPullRequest.waitCloseTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 74: global.GlobalPullRequest.idleTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 75: global.GlobalPullRequest.pauseTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 76: global.GlobalPullRequest.bufferTime:type_name -> google.protobuf.Duration
|
||||
2, // 77: global.Formily.PropertiesEntry.value:type_name -> global.Formily
|
||||
85, // 78: global.Formily.ComponentPropsEntry.value:type_name -> google.protobuf.Any
|
||||
2, // 79: global.FormilyResponse.PropertiesEntry.value:type_name -> global.Formily
|
||||
86, // 80: global.api.SysInfo:input_type -> google.protobuf.Empty
|
||||
86, // 81: global.api.DisabledPlugins:input_type -> google.protobuf.Empty
|
||||
86, // 82: global.api.Summary:input_type -> google.protobuf.Empty
|
||||
34, // 83: global.api.Shutdown:input_type -> global.RequestWithId
|
||||
34, // 84: global.api.Restart:input_type -> global.RequestWithId
|
||||
86, // 85: global.api.TaskTree:input_type -> google.protobuf.Empty
|
||||
35, // 86: global.api.StopTask:input_type -> global.RequestWithId64
|
||||
35, // 87: global.api.RestartTask:input_type -> global.RequestWithId64
|
||||
17, // 88: global.api.StreamList:input_type -> global.StreamListRequest
|
||||
86, // 89: global.api.WaitList:input_type -> google.protobuf.Empty
|
||||
20, // 90: global.api.StreamInfo:input_type -> global.StreamSnapRequest
|
||||
20, // 91: global.api.PauseStream:input_type -> global.StreamSnapRequest
|
||||
20, // 92: global.api.ResumeStream:input_type -> global.StreamSnapRequest
|
||||
50, // 93: global.api.SetStreamSpeed:input_type -> global.SetStreamSpeedRequest
|
||||
51, // 94: global.api.SeekStream:input_type -> global.SeekStreamRequest
|
||||
37, // 95: global.api.GetSubscribers:input_type -> global.SubscribersRequest
|
||||
20, // 96: global.api.AudioTrackSnap:input_type -> global.StreamSnapRequest
|
||||
20, // 97: global.api.VideoTrackSnap:input_type -> global.StreamSnapRequest
|
||||
36, // 98: global.api.ChangeSubscribe:input_type -> global.ChangeSubscribeRequest
|
||||
86, // 99: global.api.GetStreamAlias:input_type -> google.protobuf.Empty
|
||||
47, // 100: global.api.SetStreamAlias:input_type -> global.SetStreamAliasRequest
|
||||
20, // 101: global.api.StopPublish:input_type -> global.StreamSnapRequest
|
||||
34, // 102: global.api.StopSubscribe:input_type -> global.RequestWithId
|
||||
86, // 103: global.api.GetConfigFile:input_type -> google.protobuf.Empty
|
||||
7, // 104: global.api.UpdateConfigFile:input_type -> global.UpdateConfigFileRequest
|
||||
1, // 105: global.api.GetConfig:input_type -> global.GetConfigRequest
|
||||
1, // 106: global.api.GetFormily:input_type -> global.GetConfigRequest
|
||||
33, // 107: global.api.SetArming:input_type -> global.SetArmingRequest
|
||||
8, // 108: global.api.ModifyConfig:input_type -> global.ModifyConfigRequest
|
||||
86, // 109: global.api.GetPullProxyList:input_type -> google.protobuf.Empty
|
||||
42, // 110: global.api.AddPullProxy:input_type -> global.PullProxyInfo
|
||||
34, // 111: global.api.RemovePullProxy:input_type -> global.RequestWithId
|
||||
43, // 112: global.api.UpdatePullProxy:input_type -> global.UpdatePullProxyRequest
|
||||
86, // 113: global.api.GetPushProxyList:input_type -> google.protobuf.Empty
|
||||
44, // 114: global.api.AddPushProxy:input_type -> global.PushProxyInfo
|
||||
34, // 115: global.api.RemovePushProxy:input_type -> global.RequestWithId
|
||||
45, // 116: global.api.UpdatePushProxy:input_type -> global.UpdatePushProxyRequest
|
||||
86, // 117: global.api.GetRecording:input_type -> google.protobuf.Empty
|
||||
86, // 118: global.api.GetTransformList:input_type -> google.protobuf.Empty
|
||||
59, // 119: global.api.GetRecordList:input_type -> global.ReqRecordList
|
||||
59, // 120: global.api.GetEventRecordList:input_type -> global.ReqRecordList
|
||||
68, // 121: global.api.GetRecordCatalog:input_type -> global.ReqRecordCatalog
|
||||
66, // 122: global.api.DeleteRecord:input_type -> global.ReqRecordDelete
|
||||
70, // 123: global.api.GetAlarmList:input_type -> global.AlarmListRequest
|
||||
20, // 124: global.api.GetSubscriptionProgress:input_type -> global.StreamSnapRequest
|
||||
75, // 125: global.api.StartPull:input_type -> global.GlobalPullRequest
|
||||
14, // 126: global.api.SysInfo:output_type -> global.SysInfoResponse
|
||||
0, // 127: global.api.DisabledPlugins:output_type -> global.DisabledPluginsResponse
|
||||
11, // 128: global.api.Summary:output_type -> global.SummaryResponse
|
||||
32, // 129: global.api.Shutdown:output_type -> global.SuccessResponse
|
||||
32, // 130: global.api.Restart:output_type -> global.SuccessResponse
|
||||
16, // 131: global.api.TaskTree:output_type -> global.TaskTreeResponse
|
||||
32, // 132: global.api.StopTask:output_type -> global.SuccessResponse
|
||||
32, // 133: global.api.RestartTask:output_type -> global.SuccessResponse
|
||||
18, // 134: global.api.StreamList:output_type -> global.StreamListResponse
|
||||
19, // 135: global.api.WaitList:output_type -> global.StreamWaitListResponse
|
||||
21, // 136: global.api.StreamInfo:output_type -> global.StreamInfoResponse
|
||||
32, // 137: global.api.PauseStream:output_type -> global.SuccessResponse
|
||||
32, // 138: global.api.ResumeStream:output_type -> global.SuccessResponse
|
||||
32, // 139: global.api.SetStreamSpeed:output_type -> global.SuccessResponse
|
||||
32, // 140: global.api.SeekStream:output_type -> global.SuccessResponse
|
||||
40, // 141: global.api.GetSubscribers:output_type -> global.SubscribersResponse
|
||||
30, // 142: global.api.AudioTrackSnap:output_type -> global.TrackSnapShotResponse
|
||||
30, // 143: global.api.VideoTrackSnap:output_type -> global.TrackSnapShotResponse
|
||||
32, // 144: global.api.ChangeSubscribe:output_type -> global.SuccessResponse
|
||||
49, // 145: global.api.GetStreamAlias:output_type -> global.StreamAliasListResponse
|
||||
32, // 146: global.api.SetStreamAlias:output_type -> global.SuccessResponse
|
||||
32, // 147: global.api.StopPublish:output_type -> global.SuccessResponse
|
||||
32, // 148: global.api.StopSubscribe:output_type -> global.SuccessResponse
|
||||
5, // 149: global.api.GetConfigFile:output_type -> global.GetConfigFileResponse
|
||||
32, // 150: global.api.UpdateConfigFile:output_type -> global.SuccessResponse
|
||||
6, // 151: global.api.GetConfig:output_type -> global.GetConfigResponse
|
||||
6, // 152: global.api.GetFormily:output_type -> global.GetConfigResponse
|
||||
32, // 153: global.api.SetArming:output_type -> global.SuccessResponse
|
||||
32, // 154: global.api.ModifyConfig:output_type -> global.SuccessResponse
|
||||
41, // 155: global.api.GetPullProxyList:output_type -> global.PullProxyListResponse
|
||||
32, // 156: global.api.AddPullProxy:output_type -> global.SuccessResponse
|
||||
32, // 157: global.api.RemovePullProxy:output_type -> global.SuccessResponse
|
||||
32, // 158: global.api.UpdatePullProxy:output_type -> global.SuccessResponse
|
||||
46, // 159: global.api.GetPushProxyList:output_type -> global.PushProxyListResponse
|
||||
32, // 160: global.api.AddPushProxy:output_type -> global.SuccessResponse
|
||||
32, // 161: global.api.RemovePushProxy:output_type -> global.SuccessResponse
|
||||
32, // 162: global.api.UpdatePushProxy:output_type -> global.SuccessResponse
|
||||
53, // 163: global.api.GetRecording:output_type -> global.RecordingListResponse
|
||||
58, // 164: global.api.GetTransformList:output_type -> global.TransformListResponse
|
||||
62, // 165: global.api.GetRecordList:output_type -> global.RecordResponseList
|
||||
63, // 166: global.api.GetEventRecordList:output_type -> global.EventRecordResponseList
|
||||
65, // 167: global.api.GetRecordCatalog:output_type -> global.ResponseCatalog
|
||||
67, // 168: global.api.DeleteRecord:output_type -> global.ResponseDelete
|
||||
71, // 169: global.api.GetAlarmList:output_type -> global.AlarmListResponse
|
||||
74, // 170: global.api.GetSubscriptionProgress:output_type -> global.SubscriptionProgressResponse
|
||||
32, // 171: global.api.StartPull:output_type -> global.SuccessResponse
|
||||
126, // [126:172] is the sub-list for method output_type
|
||||
80, // [80:126] is the sub-list for method input_type
|
||||
80, // [80:80] is the sub-list for extension type_name
|
||||
80, // [80:80] is the sub-list for extension extendee
|
||||
0, // [0:80] is the sub-list for field type_name
|
||||
83, // 56: global.RecordFile.createdAt:type_name -> google.protobuf.Timestamp
|
||||
83, // 57: global.EventRecordFile.startTime:type_name -> google.protobuf.Timestamp
|
||||
83, // 58: global.EventRecordFile.endTime:type_name -> google.protobuf.Timestamp
|
||||
60, // 59: global.RecordResponseList.data:type_name -> global.RecordFile
|
||||
61, // 60: global.EventRecordResponseList.data:type_name -> global.EventRecordFile
|
||||
83, // 61: global.Catalog.startTime:type_name -> google.protobuf.Timestamp
|
||||
83, // 62: global.Catalog.endTime:type_name -> google.protobuf.Timestamp
|
||||
64, // 63: global.ResponseCatalog.data:type_name -> global.Catalog
|
||||
60, // 64: global.ResponseDelete.data:type_name -> global.RecordFile
|
||||
83, // 65: global.AlarmInfo.createdAt:type_name -> google.protobuf.Timestamp
|
||||
83, // 66: global.AlarmInfo.updatedAt:type_name -> google.protobuf.Timestamp
|
||||
69, // 67: global.AlarmListResponse.data:type_name -> global.AlarmInfo
|
||||
83, // 68: global.Step.startedAt:type_name -> google.protobuf.Timestamp
|
||||
83, // 69: global.Step.completedAt:type_name -> google.protobuf.Timestamp
|
||||
72, // 70: global.SubscriptionProgressData.steps:type_name -> global.Step
|
||||
73, // 71: global.SubscriptionProgressResponse.data:type_name -> global.SubscriptionProgressData
|
||||
84, // 72: global.GlobalPullRequest.delayCloseTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 73: global.GlobalPullRequest.publishTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 74: global.GlobalPullRequest.waitCloseTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 75: global.GlobalPullRequest.idleTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 76: global.GlobalPullRequest.pauseTimeout:type_name -> google.protobuf.Duration
|
||||
84, // 77: global.GlobalPullRequest.bufferTime:type_name -> google.protobuf.Duration
|
||||
2, // 78: global.Formily.PropertiesEntry.value:type_name -> global.Formily
|
||||
85, // 79: global.Formily.ComponentPropsEntry.value:type_name -> google.protobuf.Any
|
||||
2, // 80: global.FormilyResponse.PropertiesEntry.value:type_name -> global.Formily
|
||||
86, // 81: global.api.SysInfo:input_type -> google.protobuf.Empty
|
||||
86, // 82: global.api.DisabledPlugins:input_type -> google.protobuf.Empty
|
||||
86, // 83: global.api.Summary:input_type -> google.protobuf.Empty
|
||||
34, // 84: global.api.Shutdown:input_type -> global.RequestWithId
|
||||
34, // 85: global.api.Restart:input_type -> global.RequestWithId
|
||||
86, // 86: global.api.TaskTree:input_type -> google.protobuf.Empty
|
||||
35, // 87: global.api.StopTask:input_type -> global.RequestWithId64
|
||||
35, // 88: global.api.RestartTask:input_type -> global.RequestWithId64
|
||||
17, // 89: global.api.StreamList:input_type -> global.StreamListRequest
|
||||
86, // 90: global.api.WaitList:input_type -> google.protobuf.Empty
|
||||
20, // 91: global.api.StreamInfo:input_type -> global.StreamSnapRequest
|
||||
20, // 92: global.api.PauseStream:input_type -> global.StreamSnapRequest
|
||||
20, // 93: global.api.ResumeStream:input_type -> global.StreamSnapRequest
|
||||
50, // 94: global.api.SetStreamSpeed:input_type -> global.SetStreamSpeedRequest
|
||||
51, // 95: global.api.SeekStream:input_type -> global.SeekStreamRequest
|
||||
37, // 96: global.api.GetSubscribers:input_type -> global.SubscribersRequest
|
||||
20, // 97: global.api.AudioTrackSnap:input_type -> global.StreamSnapRequest
|
||||
20, // 98: global.api.VideoTrackSnap:input_type -> global.StreamSnapRequest
|
||||
36, // 99: global.api.ChangeSubscribe:input_type -> global.ChangeSubscribeRequest
|
||||
86, // 100: global.api.GetStreamAlias:input_type -> google.protobuf.Empty
|
||||
47, // 101: global.api.SetStreamAlias:input_type -> global.SetStreamAliasRequest
|
||||
20, // 102: global.api.StopPublish:input_type -> global.StreamSnapRequest
|
||||
34, // 103: global.api.StopSubscribe:input_type -> global.RequestWithId
|
||||
86, // 104: global.api.GetConfigFile:input_type -> google.protobuf.Empty
|
||||
7, // 105: global.api.UpdateConfigFile:input_type -> global.UpdateConfigFileRequest
|
||||
1, // 106: global.api.GetConfig:input_type -> global.GetConfigRequest
|
||||
1, // 107: global.api.GetFormily:input_type -> global.GetConfigRequest
|
||||
33, // 108: global.api.SetArming:input_type -> global.SetArmingRequest
|
||||
8, // 109: global.api.ModifyConfig:input_type -> global.ModifyConfigRequest
|
||||
86, // 110: global.api.GetPullProxyList:input_type -> google.protobuf.Empty
|
||||
42, // 111: global.api.AddPullProxy:input_type -> global.PullProxyInfo
|
||||
34, // 112: global.api.RemovePullProxy:input_type -> global.RequestWithId
|
||||
43, // 113: global.api.UpdatePullProxy:input_type -> global.UpdatePullProxyRequest
|
||||
86, // 114: global.api.GetPushProxyList:input_type -> google.protobuf.Empty
|
||||
44, // 115: global.api.AddPushProxy:input_type -> global.PushProxyInfo
|
||||
34, // 116: global.api.RemovePushProxy:input_type -> global.RequestWithId
|
||||
45, // 117: global.api.UpdatePushProxy:input_type -> global.UpdatePushProxyRequest
|
||||
86, // 118: global.api.GetRecording:input_type -> google.protobuf.Empty
|
||||
86, // 119: global.api.GetTransformList:input_type -> google.protobuf.Empty
|
||||
59, // 120: global.api.GetRecordList:input_type -> global.ReqRecordList
|
||||
59, // 121: global.api.GetEventRecordList:input_type -> global.ReqRecordList
|
||||
68, // 122: global.api.GetRecordCatalog:input_type -> global.ReqRecordCatalog
|
||||
66, // 123: global.api.DeleteRecord:input_type -> global.ReqRecordDelete
|
||||
70, // 124: global.api.GetAlarmList:input_type -> global.AlarmListRequest
|
||||
20, // 125: global.api.GetSubscriptionProgress:input_type -> global.StreamSnapRequest
|
||||
75, // 126: global.api.StartPull:input_type -> global.GlobalPullRequest
|
||||
14, // 127: global.api.SysInfo:output_type -> global.SysInfoResponse
|
||||
0, // 128: global.api.DisabledPlugins:output_type -> global.DisabledPluginsResponse
|
||||
11, // 129: global.api.Summary:output_type -> global.SummaryResponse
|
||||
32, // 130: global.api.Shutdown:output_type -> global.SuccessResponse
|
||||
32, // 131: global.api.Restart:output_type -> global.SuccessResponse
|
||||
16, // 132: global.api.TaskTree:output_type -> global.TaskTreeResponse
|
||||
32, // 133: global.api.StopTask:output_type -> global.SuccessResponse
|
||||
32, // 134: global.api.RestartTask:output_type -> global.SuccessResponse
|
||||
18, // 135: global.api.StreamList:output_type -> global.StreamListResponse
|
||||
19, // 136: global.api.WaitList:output_type -> global.StreamWaitListResponse
|
||||
21, // 137: global.api.StreamInfo:output_type -> global.StreamInfoResponse
|
||||
32, // 138: global.api.PauseStream:output_type -> global.SuccessResponse
|
||||
32, // 139: global.api.ResumeStream:output_type -> global.SuccessResponse
|
||||
32, // 140: global.api.SetStreamSpeed:output_type -> global.SuccessResponse
|
||||
32, // 141: global.api.SeekStream:output_type -> global.SuccessResponse
|
||||
40, // 142: global.api.GetSubscribers:output_type -> global.SubscribersResponse
|
||||
30, // 143: global.api.AudioTrackSnap:output_type -> global.TrackSnapShotResponse
|
||||
30, // 144: global.api.VideoTrackSnap:output_type -> global.TrackSnapShotResponse
|
||||
32, // 145: global.api.ChangeSubscribe:output_type -> global.SuccessResponse
|
||||
49, // 146: global.api.GetStreamAlias:output_type -> global.StreamAliasListResponse
|
||||
32, // 147: global.api.SetStreamAlias:output_type -> global.SuccessResponse
|
||||
32, // 148: global.api.StopPublish:output_type -> global.SuccessResponse
|
||||
32, // 149: global.api.StopSubscribe:output_type -> global.SuccessResponse
|
||||
5, // 150: global.api.GetConfigFile:output_type -> global.GetConfigFileResponse
|
||||
32, // 151: global.api.UpdateConfigFile:output_type -> global.SuccessResponse
|
||||
6, // 152: global.api.GetConfig:output_type -> global.GetConfigResponse
|
||||
6, // 153: global.api.GetFormily:output_type -> global.GetConfigResponse
|
||||
32, // 154: global.api.SetArming:output_type -> global.SuccessResponse
|
||||
32, // 155: global.api.ModifyConfig:output_type -> global.SuccessResponse
|
||||
41, // 156: global.api.GetPullProxyList:output_type -> global.PullProxyListResponse
|
||||
32, // 157: global.api.AddPullProxy:output_type -> global.SuccessResponse
|
||||
32, // 158: global.api.RemovePullProxy:output_type -> global.SuccessResponse
|
||||
32, // 159: global.api.UpdatePullProxy:output_type -> global.SuccessResponse
|
||||
46, // 160: global.api.GetPushProxyList:output_type -> global.PushProxyListResponse
|
||||
32, // 161: global.api.AddPushProxy:output_type -> global.SuccessResponse
|
||||
32, // 162: global.api.RemovePushProxy:output_type -> global.SuccessResponse
|
||||
32, // 163: global.api.UpdatePushProxy:output_type -> global.SuccessResponse
|
||||
53, // 164: global.api.GetRecording:output_type -> global.RecordingListResponse
|
||||
58, // 165: global.api.GetTransformList:output_type -> global.TransformListResponse
|
||||
62, // 166: global.api.GetRecordList:output_type -> global.RecordResponseList
|
||||
63, // 167: global.api.GetEventRecordList:output_type -> global.EventRecordResponseList
|
||||
65, // 168: global.api.GetRecordCatalog:output_type -> global.ResponseCatalog
|
||||
67, // 169: global.api.DeleteRecord:output_type -> global.ResponseDelete
|
||||
71, // 170: global.api.GetAlarmList:output_type -> global.AlarmListResponse
|
||||
74, // 171: global.api.GetSubscriptionProgress:output_type -> global.SubscriptionProgressResponse
|
||||
32, // 172: global.api.StartPull:output_type -> global.SuccessResponse
|
||||
127, // [127:173] is the sub-list for method output_type
|
||||
81, // [81:127] is the sub-list for method input_type
|
||||
81, // [81:81] is the sub-list for extension type_name
|
||||
81, // [81:81] is the sub-list for extension extendee
|
||||
0, // [0:81] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_global_proto_init() }
|
||||
|
||||
@@ -746,6 +746,12 @@ message RecordFile {
|
||||
string streamPath = 3;
|
||||
google.protobuf.Timestamp startTime = 4;
|
||||
google.protobuf.Timestamp endTime = 5;
|
||||
string filename = 6;
|
||||
string type = 7;
|
||||
uint32 duration = 8;
|
||||
string audioCodec = 9;
|
||||
string videoCodec = 10;
|
||||
google.protobuf.Timestamp createdAt = 11;
|
||||
}
|
||||
|
||||
message EventRecordFile {
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ func (task *ListenQuicWork) Start() (err error) {
|
||||
return
|
||||
}
|
||||
task.OnStop(task.Listener.Close)
|
||||
task.Info("listen quic on", task.ListenAddr)
|
||||
task.Info("quic start", "listen quic on", task.ListenAddr)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+201
-14
@@ -3,70 +3,156 @@ package plugin_cascade
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"m7s.live/v5"
|
||||
"m7s.live/v5/pkg/config"
|
||||
"m7s.live/v5/pkg/util"
|
||||
cascade "m7s.live/v5/plugin/cascade/pkg"
|
||||
|
||||
task "github.com/langhuihui/gotask"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
// {{ AURA-X: M1.1 - 多服务器配置 }}
|
||||
// ServerConfig 单个服务器配置(主备模式)
|
||||
type ServerConfig struct {
|
||||
Name string `json:"name" desc:"服务器名称"`
|
||||
Addr string `json:"addr" desc:"服务器地址(仅IP)"`
|
||||
QuicPort int `json:"quicPort" default:"44944" desc:"QUIC端口"`
|
||||
HttpPort int `json:"httpPort" default:"8080" desc:"HTTP端口"`
|
||||
Secret string `json:"secret" desc:"连接密钥"`
|
||||
Priority int `json:"priority" desc:"优先级 (1=最高)"`
|
||||
Enabled bool `json:"enabled" default:"true" desc:"是否启用"`
|
||||
}
|
||||
|
||||
// {{ AURA-X: GetKey 实现 util.Collection 的接口 }}
|
||||
func (sc *ServerConfig) GetKey() string {
|
||||
return sc.GetQuicAddr()
|
||||
}
|
||||
|
||||
// {{ AURA-X: GetQuicAddr 获取 QUIC 连接地址 }}
|
||||
func (sc *ServerConfig) GetQuicAddr() string {
|
||||
return fmt.Sprintf("%s:%d", sc.Addr, sc.QuicPort)
|
||||
}
|
||||
|
||||
// {{ AURA-X: GetHttpAddr 获取 HTTP 地址 }}
|
||||
func (sc *ServerConfig) GetHttpAddr() string {
|
||||
return fmt.Sprintf("http://%s:%d", sc.Addr, sc.HttpPort)
|
||||
}
|
||||
|
||||
// {{ AURA-X: M1.1 - 扩展插件配置,保留单服务器兼容 }}
|
||||
type CascadeClientPlugin struct {
|
||||
m7s.Plugin
|
||||
RelayAPI cascade.RelayAPIConfig `desc:"访问控制"`
|
||||
AutoPush bool `desc:"自动推流到上级"` //自动推流到上级
|
||||
Server string `desc:"上级服务器"` // TODO: support multiple servers
|
||||
Secret string `desc:"连接秘钥"`
|
||||
client *CascadeClient
|
||||
|
||||
// {{ AURA-X: 原有单服务器配置(保持兼容) }}
|
||||
Server string `desc:"上级服务器"` // 单服务器模式
|
||||
Secret string `desc:"连接密钥"` // 单服务器模式密钥
|
||||
|
||||
// {{ AURA-X: 新增多服务器配置(主备模式)- 用于接收YAML配置 }}
|
||||
Servers []*ServerConfig `desc:"服务器配置列表"` // 主备模式配置
|
||||
|
||||
// {{ AURA-X: M3.3 - 拉流代理同步配置 }}
|
||||
PullSyncInterval int `desc:"拉流代理同步间隔(秒)" default:"30"` // 定期同步拉流代理列表
|
||||
|
||||
// {{ AURA-X: M4 - 录像同步配置 }}
|
||||
RecordSyncInterval int `desc:"录像同步间隔(秒)" default:"60"` // 定期同步录像列表
|
||||
|
||||
// {{ AURA-X: 内部运行时集合 }}
|
||||
servers util.Collection[string, *ServerConfig] // 服务器配置集合(运行时)
|
||||
|
||||
// {{ AURA-X: 内部组件 }}
|
||||
client *CascadeClient // 单服务器模式使用
|
||||
clients task.WorkCollection[string, *CascadeClient] // 多服务器模式使用: Addr -> CascadeClient
|
||||
|
||||
// {{ AURA-X: 客户端管理器(指针) }}
|
||||
clientManager *ClientManager // ClientManager,用于多服务器的状态管理
|
||||
|
||||
// {{ AURA-X: 客户端停止事件通道,用于 CascadeClient.Dispose 通知 ClientManager }}
|
||||
clientStoppedCh chan<- *ServerConfig
|
||||
}
|
||||
|
||||
var _ = m7s.InstallPlugin[CascadeClientPlugin](m7s.PluginMeta{
|
||||
NewPuller: cascade.NewCascadePuller,
|
||||
})
|
||||
|
||||
// {{ AURA-X: M1.2 - 扩展客户端配置,添加服务器标识和密钥 }}
|
||||
type CascadeClient struct {
|
||||
task.Work
|
||||
cfg *CascadeClientPlugin
|
||||
cfg *CascadeClientPlugin
|
||||
serverID uint32 // 服务器标识(1=主, 2=备)
|
||||
serverAddr string // QUIC连接地址(如 localhost:44944)
|
||||
secret string // 连接密钥
|
||||
*quic.Conn
|
||||
}
|
||||
|
||||
// {{ AURA-X: M1.2 - 实现 GetKey 接口,供 util.Collection 使用 }}
|
||||
func (c *CascadeClient) GetKey() string {
|
||||
return c.serverAddr
|
||||
}
|
||||
|
||||
// {{ AURA-X: Dispose 实现 task.Work 的 Dispose,在连接断开时被调用 }}
|
||||
func (c *CascadeClient) Dispose() {
|
||||
// {{ AURA-X: 向 ClientManager 发送服务器停止的消息 }}
|
||||
if c.cfg.clientStoppedCh != nil {
|
||||
// {{ AURA-X: 从 servers collection 获取对应的 ServerConfig,使用 serverAddr (QuicAddr) 作为 key }}
|
||||
if serverCfg, ok := c.cfg.servers.Get(c.serverAddr); ok {
|
||||
c.cfg.clientStoppedCh <- serverCfg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// {{ AURA-X: onClientStopped 处理客户端停止事件,由 CascadeClient.Dispose 调用 }}
|
||||
func (c *CascadeClientPlugin) onClientStopped(serverCfg *ServerConfig) {
|
||||
// {{ AURA-X: 预留接口,实际处理在 ClientManager 中 }}
|
||||
}
|
||||
|
||||
func (task *CascadeClient) Start() (err error) {
|
||||
tlsConf := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"monibuca"},
|
||||
}
|
||||
cfg := task.cfg
|
||||
task.Conn, err = quic.DialAddr(cfg.Context, cfg.Server, tlsConf, &quic.Config{
|
||||
// {{ AURA-X: 复用 QUIC KeepAlive (10秒) }}
|
||||
task.Conn, err = quic.DialAddr(task.cfg.Context, task.serverAddr, tlsConf, &quic.Config{
|
||||
KeepAlivePeriod: time.Second * 10,
|
||||
EnableDatagrams: true,
|
||||
})
|
||||
if err != nil {
|
||||
task.Error("quic.DialAddr", "connect error", err.Error(), "server", task.serverAddr)
|
||||
return
|
||||
}
|
||||
var stream *quic.Stream
|
||||
if stream, err = task.OpenStreamSync(task.cfg); err == nil {
|
||||
res := []byte{0}
|
||||
fmt.Fprintf(stream, "%s", task.cfg.Secret)
|
||||
// {{ AURA-X: 获取对应服务器的 Secret }}
|
||||
secret := task.getSecret()
|
||||
fmt.Fprintf(stream, "%s", secret)
|
||||
stream.Write([]byte{0})
|
||||
_, err = stream.Read(res)
|
||||
if err == nil && res[0] == 0 {
|
||||
task.Info("connected to cascade server", "server", task.cfg.Server)
|
||||
task.Info("connected to cascade server", "server", task.serverAddr, "id", task.serverID)
|
||||
stream.Close()
|
||||
} else {
|
||||
var zapErr any = err
|
||||
if err == nil {
|
||||
zapErr = res[0]
|
||||
}
|
||||
task.Error("connect to cascade server", "server", task.cfg.Server, "err", zapErr)
|
||||
task.Error("connect to cascade server", "server", task.serverAddr, "err", zapErr)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (task *CascadeClient) Run() (err error) {
|
||||
// {{ AURA-X: M1.3 - 获取 Secret(简化版) }}
|
||||
func (task *CascadeClient) getSecret() string {
|
||||
return task.secret
|
||||
}
|
||||
|
||||
func (task *CascadeClient) Go() (err error) {
|
||||
for err == nil {
|
||||
var s *quic.Stream
|
||||
if s, err = task.AcceptStream(task.Task.Context); err == nil {
|
||||
@@ -81,22 +167,123 @@ func (task *CascadeClient) Run() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// {{ AURA-X: M1.3 - 双模式启动逻辑 }}
|
||||
func (c *CascadeClientPlugin) Start() (err error) {
|
||||
if c.Secret == "" && c.Server == "" {
|
||||
return nil
|
||||
c.AddTask(&c.clients)
|
||||
// {{ AURA-X: 模式1 - 单服务器模式(原有逻辑) }}
|
||||
if c.Server != "" && len(c.Servers) == 0 {
|
||||
if c.Secret == "" {
|
||||
c.Warn("secret is empty, skip connecting")
|
||||
return nil
|
||||
}
|
||||
c.Info("running in single server mode", "server", c.Server)
|
||||
return c.startSingleServer()
|
||||
}
|
||||
|
||||
// {{ AURA-X: 模式2 - 主备模式(新增逻辑) }}
|
||||
if len(c.Servers) > 0 {
|
||||
c.Info("running in multi-server failover mode")
|
||||
return c.startMultiServer()
|
||||
}
|
||||
|
||||
// {{ AURA-X: 无配置,不启动 }}
|
||||
c.Warn("no server configured, skip starting")
|
||||
return nil
|
||||
}
|
||||
|
||||
// {{ AURA-X: M1.3 - 单服务器模式(复用原有逻辑) }}
|
||||
func (c *CascadeClientPlugin) startSingleServer() (err error) {
|
||||
connectTask := CascadeClient{
|
||||
cfg: c,
|
||||
cfg: c,
|
||||
serverAddr: c.Server,
|
||||
secret: c.Secret,
|
||||
}
|
||||
connectTask.SetRetry(-1, time.Second)
|
||||
connectTask.SetDescription("serverAddr", c.Server)
|
||||
connectTask.SetDescription("secret", c.Secret)
|
||||
c.AddTask(&connectTask)
|
||||
c.client = &connectTask
|
||||
return
|
||||
}
|
||||
|
||||
// {{ AURA-X: M1.4 - 多服务器模式(新增逻辑) }}
|
||||
// 注意:这里只做配置检查,不启动 client,启动由 ClientManager 负责
|
||||
func (c *CascadeClientPlugin) startMultiServer() (err error) {
|
||||
// {{ AURA-X: 检查 Secret 是否重复 }}
|
||||
secretMap := make(map[string]int) // secret -> 出现次数
|
||||
for i := range c.Servers {
|
||||
if c.Servers[i].Secret != "" {
|
||||
secretMap[c.Servers[i].Secret]++
|
||||
}
|
||||
}
|
||||
for secret, count := range secretMap {
|
||||
if count > 1 {
|
||||
c.Error("duplicate secret detected, failover requires unique secrets", "secret", secret)
|
||||
return fmt.Errorf("duplicate secret detected: %s (count: %d), please use unique secrets for each server", secret, count)
|
||||
}
|
||||
}
|
||||
|
||||
// {{ AURA-X: 按优先级排序 }}
|
||||
sortedServers := make([]*ServerConfig, len(c.Servers))
|
||||
copy(sortedServers, c.Servers)
|
||||
sort.Slice(sortedServers, func(i, j int) bool {
|
||||
return sortedServers[i].Priority < sortedServers[j].Priority
|
||||
})
|
||||
|
||||
// {{ AURA-X: 输出主备服务器信息 }}
|
||||
if len(sortedServers) > 0 {
|
||||
primary := sortedServers[0]
|
||||
c.Info("primary server", "name", primary.Name, "addr", primary.Addr)
|
||||
if len(sortedServers) > 1 {
|
||||
backup := sortedServers[1]
|
||||
c.Info("backup server", "name", backup.Name, "addr", backup.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
// {{ AURA-X: 转换为 util.Collection 供运行时使用 }}
|
||||
c.servers = util.Collection[string, *ServerConfig]{}
|
||||
for i := range sortedServers {
|
||||
c.servers.Set(sortedServers[i])
|
||||
}
|
||||
|
||||
// {{ AURA-X: 创建 ServerRunner 用于追踪服务器在线状态 }}
|
||||
// {{ AURA-X: 使用插件自带的数据库连接 c.DB }}
|
||||
var serverRunner *cascade.ServerRunner
|
||||
if c.DB != nil {
|
||||
serverRunner = cascade.NewServerRunner(c.DB)
|
||||
}
|
||||
|
||||
// {{ AURA-X: 启动 ClientManager,由它负责连接管理 }}
|
||||
c.clientManager = NewClientManager(c, serverRunner, c.DB)
|
||||
c.AddTask(c.clientManager)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// {{ AURA-X: M1.5 - 支持多服务器拉流 }}
|
||||
func (c *CascadeClientPlugin) Pull(streamPath string, conf config.Pull, pub *config.Publish) (job *m7s.PullJob, err error) {
|
||||
var conn *quic.Conn
|
||||
|
||||
// {{ AURA-X: 多服务器模式:从当前运行的服务器获取连接 }}
|
||||
if c.clients.Length() > 0 {
|
||||
// {{ AURA-X: 从 ClientManager 获取当前运行的服务器地址 }}
|
||||
runningAddr := c.clientManager.GetRunningServer()
|
||||
if runningAddr != "" {
|
||||
if client, ok := c.clients.Get(runningAddr); ok {
|
||||
conn = client.Conn
|
||||
}
|
||||
}
|
||||
} else if c.client != nil {
|
||||
// {{ AURA-X: 单服务器模式:使用原有 client }}
|
||||
conn = c.client.Conn
|
||||
}
|
||||
|
||||
if conn == nil {
|
||||
return nil, fmt.Errorf("no available server connection")
|
||||
}
|
||||
|
||||
puller := &cascade.Puller{
|
||||
Conn: c.client.Conn,
|
||||
Conn: conn,
|
||||
}
|
||||
job = puller.GetPullJob()
|
||||
job.Init(puller, &c.Plugin, streamPath, conf, pub)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,3 +35,31 @@ func (p *Puller) Start() (err error) {
|
||||
_, err = fmt.Fprintf(stream, "%s %s\r\n", "PULLFLV", p.PullJob.Publisher.StreamPath)
|
||||
return
|
||||
}
|
||||
|
||||
// Dispose 发送 STOPFLV 命令,然后关闭 Stream
|
||||
func (p *Puller) Dispose() {
|
||||
if p.ReadCloser != nil {
|
||||
// 先发送停止命令
|
||||
if w, ok := p.ReadCloser.(interface{ Write([]byte) (int, error) }); ok {
|
||||
fmt.Fprintf(w, "STOPFLV %s\r\n", p.PullJob.Publisher.StreamPath)
|
||||
fmt.Printf("[cascade] 发送 STOPFLV: %s\n", p.PullJob.Publisher.StreamPath)
|
||||
}
|
||||
// 关闭 Stream
|
||||
p.ReadCloser.Close()
|
||||
p.ReadCloser = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Dispose 发送 STOPFLV 命令,然后关闭 Stream
|
||||
func (p *Puller) Dispose() {
|
||||
if p.ReadCloser != nil {
|
||||
// 先发送停止命令
|
||||
if w, ok := p.ReadCloser.(interface{ Write([]byte) (int, error) }); ok {
|
||||
fmt.Fprintf(w, "STOPFLV %s\r\n", p.PullJob.Publisher.StreamPath)
|
||||
fmt.Printf("[cascade] 发送 STOPFLV: %s\n", p.PullJob.Publisher.StreamPath)
|
||||
}
|
||||
// 关闭 Stream
|
||||
p.ReadCloser.Close()
|
||||
p.ReadCloser = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cascade
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -67,6 +69,25 @@ func (task *ReceiveRequestTask) Go() (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 保存 subscriber 引用,用于手动停止
|
||||
subscriber := live.Subscriber
|
||||
// 启动 goroutine 监听停止命令
|
||||
go func() {
|
||||
stopReader := bufio.NewReader(task.Stream)
|
||||
for {
|
||||
line, _, err := stopReader.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(string(line), "STOPFLV") {
|
||||
fmt.Printf("[cascade] 收到 STOPFLV: %s\n", line)
|
||||
// 直接调用 subscriber.Stop() 停止订阅者
|
||||
subscriber.Stop(errors.New("stop by STOPFLV command"))
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
return live.Run()
|
||||
}
|
||||
req, err = http.NewRequestWithContext(ctx, reqLine[0], reqLine[1], reader)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package cascade
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ServerRunRecord 服务器运行记录(数据库模型)
|
||||
type ServerRunRecord struct {
|
||||
gorm.Model
|
||||
QuicAddr string // 服务器QUIC地址 (IP:Port)
|
||||
Priority int // 优先级 (1=主, 2=备)
|
||||
StartTime time.Time // 服务器开始运行时间(连接成功时间)
|
||||
EndTime *time.Time // 服务器结束运行时间(断开时间),nil表示当前在线
|
||||
}
|
||||
|
||||
// {{ AURA-X: 实现 GetKey 接口,供 util.Collection 使用 }}
|
||||
func (r *ServerRunRecord) GetKey() string {
|
||||
return r.QuicAddr
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package cascade
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"m7s.live/v5/pkg/util"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// {{ AURA-X: M2 - 服务器运行状态追踪 }}
|
||||
// ServerRunner 记录每台服务器的运行时间段(在线/离线),持久化到数据库
|
||||
|
||||
// ServerRunner 服务器运行状态追踪器
|
||||
type ServerRunner struct {
|
||||
records util.Collection[string, *ServerRunRecord] // quicAddr -> 运行记录(内存缓存)
|
||||
db *gorm.DB // 数据库连接
|
||||
}
|
||||
|
||||
// NewServerRunner 创建新的服务器运行追踪器
|
||||
// {{ AURA-X: 将在 M3 的 FailoverController 中被调用 }}
|
||||
func NewServerRunner(db *gorm.DB) *ServerRunner {
|
||||
sr := &ServerRunner{
|
||||
records: util.Collection[string, *ServerRunRecord]{},
|
||||
db: db,
|
||||
}
|
||||
|
||||
// {{ AURA-X: 自动迁移数据库表 }}
|
||||
sr.db.AutoMigrate(&ServerRunRecord{})
|
||||
|
||||
// {{ AURA-X: 加载所有离线记录到内存(在线的只保留最新一条) }}
|
||||
sr.loadFromDB()
|
||||
|
||||
return sr
|
||||
}
|
||||
|
||||
// loadFromDB 从数据库加载记录到内存
|
||||
// {{ AURA-X: 在线记录只保留最新的,离线记录全部加载 }}
|
||||
func (sr *ServerRunner) loadFromDB() {
|
||||
var allRecords []ServerRunRecord
|
||||
sr.db.Order("start_time asc").Find(&allRecords)
|
||||
|
||||
// 按 quicAddr 分组,只保留每个服务器的最新记录
|
||||
latestByServer := make(map[string]*ServerRunRecord)
|
||||
for i := range allRecords {
|
||||
record := &allRecords[i]
|
||||
// 如果记录还未结束(在线),只保留最新的
|
||||
if record.EndTime == nil {
|
||||
if existing, ok := latestByServer[record.QuicAddr]; !ok || existing.StartTime.Before(record.StartTime) {
|
||||
latestByServer[record.QuicAddr] = record
|
||||
}
|
||||
} else {
|
||||
// 离线记录全部加载
|
||||
sr.records.Set(record)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加在线记录
|
||||
for _, record := range latestByServer {
|
||||
sr.records.Set(record)
|
||||
}
|
||||
}
|
||||
|
||||
// RecordStart 记录服务器开始运行(连接成功)
|
||||
func (sr *ServerRunner) RecordStart(quicAddr string, priority int) {
|
||||
now := time.Now()
|
||||
|
||||
// 如果已有记录且未结束,先结束它
|
||||
if existing, ok := sr.records.Get(quicAddr); ok && existing.EndTime == nil {
|
||||
existing.EndTime = &now
|
||||
// {{ AURA-X: 更新数据库 }}
|
||||
sr.db.Model(existing).Update("end_time", now)
|
||||
}
|
||||
|
||||
// 创建新的运行记录
|
||||
record := &ServerRunRecord{
|
||||
QuicAddr: quicAddr,
|
||||
Priority: priority,
|
||||
StartTime: now,
|
||||
EndTime: nil,
|
||||
}
|
||||
|
||||
// {{ AURA-X: 保存到数据库 }}
|
||||
sr.db.Create(record)
|
||||
|
||||
// {{ AURA-X: 添加到内存缓存 }}
|
||||
sr.records.Set(record)
|
||||
}
|
||||
|
||||
// RecordStop 记录服务器停止运行(断开连接)
|
||||
func (sr *ServerRunner) RecordStop(quicAddr string) {
|
||||
now := time.Now()
|
||||
|
||||
// 更新现有记录
|
||||
if existing, ok := sr.records.Get(quicAddr); ok && existing.EndTime == nil {
|
||||
existing.EndTime = &now
|
||||
// {{ AURA-X: 更新数据库 }}
|
||||
sr.db.Model(existing).Update("end_time", now)
|
||||
// {{ AURA-X: 从内存缓存中移除(因为已离线) }}
|
||||
sr.records.RemoveByKey(quicAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// IsOnline 检查服务器是否在线
|
||||
func (sr *ServerRunner) IsOnline(quicAddr string) bool {
|
||||
if record, ok := sr.records.Get(quicAddr); ok {
|
||||
return record.EndTime == nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetRecord 获取服务器的当前运行记录
|
||||
func (sr *ServerRunner) GetRecord(quicAddr string) *ServerRunRecord {
|
||||
if record, ok := sr.records.Get(quicAddr); ok {
|
||||
return record
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllRecords 获取所有历史运行记录
|
||||
func (sr *ServerRunner) GetAllRecords() []ServerRunRecord {
|
||||
var records []ServerRunRecord
|
||||
sr.db.Order("start_time desc").Find(&records)
|
||||
return records
|
||||
}
|
||||
|
||||
// GetOnlineServers 获取当前所有在线服务器地址
|
||||
func (sr *ServerRunner) GetOnlineServers() []string {
|
||||
var online []string
|
||||
sr.records.Range(func(record *ServerRunRecord) bool {
|
||||
if record.EndTime == nil {
|
||||
online = append(online, record.QuicAddr)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return online
|
||||
}
|
||||
|
||||
// QueryByTimeRange 查询指定时间范围内的运行记录
|
||||
func (sr *ServerRunner) QueryByTimeRange(start, end time.Time) []ServerRunRecord {
|
||||
var records []ServerRunRecord
|
||||
sr.db.Where("start_time >= ? AND start_time <= ?", start, end).Order("start_time asc").Find(&records)
|
||||
return records
|
||||
}
|
||||
|
||||
// GetServerHistory 获取指定服务器的所有历史记录
|
||||
func (sr *ServerRunner) GetServerHistory(quicAddr string) []ServerRunRecord {
|
||||
var records []ServerRunRecord
|
||||
sr.db.Where("quic_addr = ?", quicAddr).Order("start_time desc").Find(&records)
|
||||
return records
|
||||
}
|
||||
|
||||
// Clear 清除所有运行记录(仅用于测试)
|
||||
func (sr *ServerRunner) Clear() {
|
||||
sr.db.Where("1=1").Delete(&ServerRunRecord{})
|
||||
sr.records = util.Collection[string, *ServerRunRecord]{}
|
||||
}
|
||||
+51
-2
@@ -51,8 +51,57 @@ func (p *MP4Plugin) downloadSingleFile(stream *m7s.RecordStream, flag mp4.Flag,
|
||||
var file storage.File
|
||||
var err error
|
||||
|
||||
// 最高优先级:如果 FilePath 是绝对路径,直接使用,跳过所有 storage 处理
|
||||
if filepath.IsAbs(stream.FilePath) {
|
||||
// 最高优先级:如果 FilePath 是 HTTP/HTTPS URL,直接读取
|
||||
if strings.HasPrefix(stream.FilePath, "http://") || strings.HasPrefix(stream.FilePath, "https://") {
|
||||
if flag == 0 {
|
||||
// 普通 MP4:重定向到 URL,让客户端直接从源获取
|
||||
p.Info("redirecting to http URL", "url", stream.FilePath)
|
||||
http.Redirect(w, r, stream.FilePath, http.StatusFound)
|
||||
return
|
||||
}
|
||||
// fMP4:下载文件到临时文件后处理
|
||||
resp, err := http.Get(stream.FilePath)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to download file: %v", err), http.StatusInternalServerError)
|
||||
p.Error("failed to download file from URL", "err", err, "url", stream.FilePath)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 创建临时文件
|
||||
tmpFile, err := os.CreateTemp("", "mp4-*.tmp")
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to create temp file: %v", err), http.StatusInternalServerError)
|
||||
p.Error("failed to create temp file", "err", err)
|
||||
return
|
||||
}
|
||||
tmpPath := tmpFile.Name()
|
||||
|
||||
// 复制内容到临时文件
|
||||
_, err = io.Copy(tmpFile, resp.Body)
|
||||
tmpFile.Close()
|
||||
if err != nil {
|
||||
os.Remove(tmpPath)
|
||||
http.Error(w, fmt.Sprintf("failed to save downloaded file: %v", err), http.StatusInternalServerError)
|
||||
p.Error("failed to save downloaded file", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 打开临时文件进行 fMP4 转换
|
||||
file, err = os.Open(tmpPath)
|
||||
if err != nil {
|
||||
os.Remove(tmpPath)
|
||||
http.Error(w, fmt.Sprintf("failed to open downloaded file: %v", err), http.StatusInternalServerError)
|
||||
p.Error("failed to open downloaded file", "err", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
file.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
p.Info("reading downloaded file for fmp4 conversion from URL", "url", stream.FilePath)
|
||||
// 继续执行 fMP4 转换处理
|
||||
} else if filepath.IsAbs(stream.FilePath) {
|
||||
if flag == 0 {
|
||||
// 普通 MP4:直接 ServeFile
|
||||
http.ServeFile(w, r, stream.FilePath)
|
||||
|
||||
@@ -2,14 +2,18 @@ package mp4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"m7s.live/v5/pkg/storage"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"m7s.live/v5/pkg/storage"
|
||||
|
||||
"m7s.live/v5"
|
||||
"m7s.live/v5/pkg"
|
||||
"m7s.live/v5/pkg/codec"
|
||||
@@ -41,7 +45,48 @@ func (d *DemuxerRange) Demux(ctx context.Context) error {
|
||||
if stream.EndTime.Before(d.StartTime) || stream.StartTime.After(d.EndTime) {
|
||||
continue
|
||||
}
|
||||
if filepath.IsAbs(stream.FilePath) {
|
||||
// 如果是 HTTP/HTTPS URL,下载到临时文件
|
||||
if strings.HasPrefix(stream.FilePath, "http://") || strings.HasPrefix(stream.FilePath, "https://") {
|
||||
resp, err := http.Get(stream.FilePath)
|
||||
if err != nil {
|
||||
d.Error("failed to download file from URL", "err", err, "url", stream.FilePath)
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 创建临时文件
|
||||
tmpFile, err := os.CreateTemp("", "mp4-*.tmp")
|
||||
if err != nil {
|
||||
d.Error("failed to create temp file", "err", err)
|
||||
continue
|
||||
}
|
||||
tmpPath := tmpFile.Name()
|
||||
|
||||
// 复制内容到临时文件
|
||||
_, err = io.Copy(tmpFile, resp.Body)
|
||||
tmpFile.Close()
|
||||
if err != nil {
|
||||
os.Remove(tmpPath)
|
||||
d.Error("failed to save downloaded file", "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 打开临时文件
|
||||
file, err = os.Open(tmpPath)
|
||||
if err != nil {
|
||||
os.Remove(tmpPath)
|
||||
d.Error("failed to open downloaded file", "err", err)
|
||||
continue
|
||||
}
|
||||
// 延迟关闭和删除临时文件
|
||||
defer func() {
|
||||
if file != nil {
|
||||
file.Close()
|
||||
}
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
d.Info("reading downloaded file from URL", "url", stream.FilePath)
|
||||
} else if filepath.IsAbs(stream.FilePath) {
|
||||
file, err = os.Open(stream.FilePath)
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user