doc: add arch docs

This commit is contained in:
langhuihui
2025-01-30 18:09:11 +08:00
parent 3c2f87d38d
commit dc2995daf0
25 changed files with 2588 additions and 76 deletions

111
doc/arch/admin.md Normal file
View File

@@ -0,0 +1,111 @@
# Admin Service Mechanism
Monibuca provides powerful administrative service support for system monitoring, configuration management, plugin management, and other administrative functions. This document details the implementation mechanism and usage of the Admin service.
## Service Architecture
### 1. UI Interface
The Admin service provides a Web management interface by loading the `admin.zip` file. This interface has the following features:
- Unified management interface entry point
- Access to all server-provided HTTP interfaces
- Responsive design, supporting various devices
- Modular function organization
### 2. Configuration Management
Admin service configuration is located in the admin node within global configuration, including:
```yaml
admin:
enableLogin: false # Whether to enable login mechanism
filePath: admin.zip # Management interface file path
homePage: home # Management interface homepage
users: # User list (effective only when login is enabled)
- username: admin # Username
password: admin # Password
role: admin # Role, options: admin, user
```
When `enableLogin` is false, all users access as anonymous users.
When login is enabled and no users exist in the database, the system automatically creates a default admin account (username: admin, password: admin).
### 3. Authentication Mechanism
Admin provides dedicated user login verification interfaces for:
- User identity verification
- Access token management (JWT)
- Permission control
- Session management
### 4. Interface Specifications
All Admin APIs must follow these specifications:
- Response format uniformly includes code, message, and data fields
- Successful responses use code = 0
- Error handling uses unified error response format
- Must perform permission verification
## Function Modules
### 1. System Monitoring
- CPU usage monitoring
- Memory usage
- Network bandwidth statistics
- Disk usage
- System uptime
- Online user statistics
### 2. Plugin Management
- Plugin enable/disable
- Plugin configuration modification
- Plugin status viewing
- Plugin version management
- Plugin dependency checking
### 3. Stream Media Management
- Online stream list viewing
- Stream status monitoring
- Stream control (start/stop)
- Stream information statistics
- Recording management
- Transcoding task management
## Security Mechanism
### 1. Authentication Mechanism
- JWT token authentication
- Session timeout control
- IP whitelist control
### 2. Permission Control
- Role-Based Access Control (RBAC)
- Fine-grained permission management
- Operation audit logging
- Sensitive operation confirmation
## Best Practices
1. Security
- Use HTTPS encryption
- Implement strong password policies
- Regular key updates
- Monitor abnormal access
2. Performance Optimization
- Reasonable caching strategy
- Paginated query optimization
- Asynchronous processing of time-consuming operations
3. Maintainability
- Complete operation logs
- Clear error messages
- Hot configuration updates

157
doc/arch/alias.md Normal file
View File

@@ -0,0 +1,157 @@
# Monibuca Stream Alias Technical Implementation Documentation
## 1. Feature Overview
Stream Alias is an important feature in Monibuca that allows creating one or more aliases for existing streams, enabling the same stream to be accessed through different paths. This feature is particularly useful in the following scenarios:
- Creating short aliases for streams with long paths
- Dynamically modifying stream access paths
- Implementing stream redirection functionality
## 2. Core Data Structures
### 2.1 AliasStream Structure
```go
type AliasStream struct {
*Publisher // Inherits from Publisher
AutoRemove bool // Whether to automatically remove
StreamPath string // Original stream path
Alias string // Alias path
}
```
### 2.2 StreamAlias Message Structure
```protobuf
message StreamAlias {
string streamPath = 1; // Original stream path
string alias = 2; // Alias
bool autoRemove = 3; // Whether to automatically remove
uint32 status = 4; // Status
}
```
## 3. Core Functionality Implementation
### 3.1 Alias Creation and Modification
When calling the `SetStreamAlias` API to create or modify an alias, the system:
1. Validates and parses the target stream path
2. Checks if the target stream exists
3. Handles the following scenarios:
- Modifying existing alias: Updates auto-remove flag and stream path
- Creating new alias: Initializes new AliasStream structure
4. Handles subscriber transfer or wakes waiting subscribers
### 3.2 Publisher Startup Alias Handling
When a Publisher starts, the system:
1. Checks for aliases pointing to this Publisher
2. For each matching alias:
- If alias Publisher is empty, sets it to the new Publisher
- If alias already has a Publisher, transfers subscribers to the new Publisher
3. Wakes all subscribers waiting for this stream
### 3.3 Publisher Destruction Alias Handling
Publisher destruction process:
1. Checks if stopped due to being kicked out
2. Removes Publisher from Streams
3. Iterates through all aliases, for those pointing to this Publisher:
- If auto-remove is set, deletes the alias
- Otherwise, retains alias structure
4. Handles related subscribers
### 3.4 Subscriber Handling Mechanism
When a new subscription request arrives:
1. Checks for matching alias
2. If alias exists:
- If alias Publisher exists: adds subscriber
- If Publisher doesn't exist: triggers OnSubscribe event
3. If no alias exists:
- Checks for matching regex alias
- Checks if original stream exists
- Adds subscriber or joins wait list based on conditions
## 4. API Interfaces
### 4.1 Set Alias
```http
POST /api/stream/alias
```
Request body:
```json
{
"streamPath": "original stream path",
"alias": "alias path",
"autoRemove": false
}
```
### 4.2 Get Alias List
```http
GET /api/stream/alias
```
Response body:
```json
{
"code": 0,
"message": "",
"data": [
{
"streamPath": "original stream path",
"alias": "alias path",
"autoRemove": false,
"status": 1
}
]
}
```
## 5. Status Descriptions
Alias status descriptions:
- 0: Initial state
- 1: Alias associated with Publisher
- 2: Original stream with same name exists
## 6. Best Practices
1. Using Auto-Remove (autoRemove)
- Enable auto-remove when temporary stream redirection is needed
- This ensures automatic alias cleanup when original stream ends
2. Alias Naming Recommendations
- Use short, meaningful aliases
- Avoid special characters
- Use standardized path format
3. Performance Considerations
- Alias mechanism uses efficient memory mapping
- Maintains connection state during subscriber transfer
- Supports dynamic modification without service restart
## 7. Important Notes
1. Alias Conflict Handling
- System handles appropriately when created alias conflicts with existing stream path
- Recommended to check for conflicts before creating aliases
2. Subscriber Behavior
- Existing subscribers are transferred to new stream when alias is modified
- Ensure clients can handle stream redirection
3. Resource Management
- Clean up unnecessary aliases promptly
- Use auto-remove feature appropriately
- Monitor alias status to avoid resource leaks

245
doc/arch/config.md Normal file
View File

@@ -0,0 +1,245 @@
# Monibuca Configuration Mechanism
Monibuca employs a flexible configuration system that supports multiple configuration methods. Configuration files use the YAML format and can be initialized either through files or by directly passing configuration objects.
## Configuration Loading Process
1. Configuration initialization occurs during Server startup and can be provided through one of three methods:
- YAML configuration file path
- Byte array containing YAML configuration content
- Raw configuration object (RawConfig)
2. Configuration parsing process:
```go
// Supports three configuration input methods
case string: // Configuration file path
case []byte: // YAML configuration content
case RawConfig: // Raw configuration object
```
## Configuration Structure
### Simplified Configuration Syntax
When a configuration item's value is a struct or map type, the system supports a simplified configuration approach: if a simple type value is configured directly, that value will be automatically assigned to the first field of the struct.
For example, given the following struct:
```go
type Config struct {
Port int
Host string
}
```
You can use simplified syntax:
```yaml
plugin: 1935 # equivalent to plugin: { port: 1935 }
```
### Configuration Deserialization Mechanism
Each plugin contains a `config.Config` type field for storing and managing configuration information. The configuration loading priority from highest to lowest is:
1. User configuration (via `ParseUserFile`)
2. Default configuration (via `ParseDefaultYaml`)
3. Global configuration (via `ParseGlobal`)
4. Plugin-specific configuration (via `Parse`)
5. Common configuration (via `Parse`)
Configurations are automatically deserialized into the plugin's public properties. For example:
```go
type MyPlugin struct {
Plugin
Port int `yaml:"port"`
Host string `yaml:"host"`
}
```
Corresponding YAML configuration:
```yaml
myplugin:
port: 8080
host: "localhost"
```
The configuration will automatically deserialize to the `Port` and `Host` fields. You can query configurations using methods provided by `Config`:
- `Has(name string)` - Check if a configuration exists
- `Get(name string)` - Get the value of a configuration
- `GetMap()` - Get a map of all configurations
Additionally, plugin configurations support saving modifications:
```go
func (p *Plugin) SaveConfig() (err error)
```
This saves the modified configuration to `{settingDir}/{pluginName}.yaml`.
### Global Configuration
Global configuration is located under the `global` node in the YAML file and includes these main configuration items:
```yaml
global:
settingDir: ".m7s" # Settings directory
fatalDir: "fatal" # Error log directory
pulseInterval: "5s" # Heartbeat interval
disableAll: false # Whether to disable all plugins
streamAlias: # Stream alias configuration
pattern: "target" # Regex -> target path
location: # HTTP routing rules
pattern: "target" # Regex -> target address
admin: # Admin interface configuration
enableLogin: false # Whether to enable login mechanism
filePath: "admin.zip" # Admin interface file path
homePage: "home" # Admin interface homepage
users: # User list (effective only when login is enabled)
- username: "admin" # Username
password: "admin" # Password
role: "admin" # Role (admin/user)
```
### Database Configuration
If database connection is configured, the system will automatically:
1. Connect to the database
2. Auto-migrate data models
3. Initialize user data (if login is enabled)
4. Initialize proxy configurations
```yaml
global:
db:
dsn: "" # Database connection string
type: "" # Database type
```
### Proxy Configuration
The system supports pull and push proxy configurations:
```yaml
global:
pullProxy: # Pull proxy configuration
- id: 1 # Proxy ID
name: "proxy1" # Proxy name
url: "rtmp://..." # Proxy address
type: "rtmp" # Proxy type
pullOnStart: true # Whether to pull on startup
pushProxy: # Push proxy configuration
- id: 1 # Proxy ID
name: "proxy1" # Proxy name
url: "rtmp://..." # Proxy address
type: "rtmp" # Proxy type
pushOnStart: true # Whether to push on startup
audio: true # Whether to push audio
```
## Plugin Configuration
Each plugin can have its own configuration node, named as the lowercase version of the plugin name:
```yaml
rtmp: # RTMP plugin configuration
port: 1935 # Listen port
rtsp: # RTSP plugin configuration
port: 554 # Listen port
```
## Configuration Priority
The configuration system uses a multi-level priority mechanism, from highest to lowest:
1. URL Query Parameter Configuration - Configurations specified via URL query parameters during publishing or subscribing have the highest priority
```
Example: rtmp://localhost/live/stream?audio=false
```
2. Plugin-Specific Configuration - Configuration items under the plugin's configuration node
```yaml
rtmp:
publish:
audio: true
subscribe:
audio: true
```
3. Global Configuration - Configuration items under the global node
```yaml
global:
publish:
audio: true
subscribe:
audio: true
```
## Common Configuration
There are some common configuration items that can appear in both global and plugin configurations. When plugins use these items, they prioritize values from plugin configuration, falling back to global configuration if not set in plugin configuration.
Main common configurations include:
1. Publish Configuration
```yaml
publish:
audio: true # Whether to include audio
video: true # Whether to include video
bufferLength: 1000 # Buffer length
```
2. Subscribe Configuration
```yaml
subscribe:
audio: true # Whether to subscribe to audio
video: true # Whether to subscribe to video
bufferLength: 1000 # Buffer length
```
3. HTTP Configuration
```yaml
http:
listenAddr: ":8080" # Listen address
```
4. Other Common Configurations
- PublicIP - Public IP
- PublicIPv6 - Public IPv6
- LogLevel - Log level
- EnableAuth - Whether to enable authentication
Usage example:
```yaml
# Global configuration
global:
publish:
audio: true
video: true
subscribe:
audio: true
video: true
# Plugin configuration (higher priority than global)
rtmp:
publish:
audio: false # Overrides global configuration
subscribe:
video: false # Overrides global configuration
# URL query parameters (highest priority)
# rtmp://localhost/live/stream?audio=true&video=false
```
## Hot Configuration Update
Currently, the system supports hot updates for the admin interface file (admin.zip), periodically checking for changes and automatically reloading.
## Configuration Validation
The system performs basic validation of configurations at startup:
1. Checks necessary directory permissions
2. Validates database connections
3. Validates user configurations (if login is enabled)

94
doc/arch/db.md Normal file
View File

@@ -0,0 +1,94 @@
# Database Mechanism
Monibuca provides database support functionality, allowing database configuration and usage in both global settings and plugins.
## Configuration
### Global Configuration
Database can be configured in global settings using these fields:
```yaml
global:
dsn: "database connection string"
dbType: "database type"
```
### Plugin Configuration
Each plugin can have its own database configuration:
```yaml
pluginName:
dsn: "database connection string"
dbType: "database type"
```
## Database Initialization Process
### Global Database Initialization
1. When the server starts, if `dsn` is configured, it attempts to connect to the database
2. After successful connection, the following models are automatically migrated:
- User table
- PullProxy table
- PushProxy table
- StreamAliasDB table
3. If login is enabled (`Admin.EnableLogin = true`), users are created or updated based on the configuration file
4. If no users exist in the database, a default admin account is created:
- Username: admin
- Password: admin
- Role: admin
### Plugin Database Initialization
1. During plugin initialization, the plugin's `dsn` configuration is checked
2. If the plugin's `dsn` matches the global configuration, the global database connection is used
3. If the plugin configures a different `dsn`, a new database connection is created
4. If the plugin implements the Recorder interface, the RecordStream table is automatically migrated
## Database Usage
### Global Database Access
The global database can be accessed through the Server instance:
```go
server.DB
```
### Plugin Database Access
Plugins can access their database through their instance:
```go
plugin.DB
```
## Important Notes
1. Database connection failures will disable related functionality
2. Plugins using independent databases need to manage their own database connections
3. Database migration failures will cause plugins to be disabled
4. It's recommended to reuse the global database connection when possible to avoid creating too many connections
## Built-in Tables
### User Table
Stores user information, including:
- Username: User's name
- Password: User's password
- Role: User's role (admin/user)
### PullProxy Table
Stores pull proxy configurations
### PushProxy Table
Stores push proxy configurations
### StreamAliasDB Table
Stores stream alias configurations
### RecordStream Table
Stores recording-related information (only created when plugin implements Recorder interface)

72
doc/arch/grpc.md Normal file
View File

@@ -0,0 +1,72 @@
# gRPC Service Mechanism
Monibuca provides gRPC service support, allowing plugins to offer services via the gRPC protocol. This document explains the implementation mechanism and usage of gRPC services.
## Service Registration Mechanism
### 1. Service Registration
Plugins need to pass ServiceDesc and Handler when calling `InstallPlugin` to register gRPC services:
```go
// Example: Registering gRPC service in a plugin
type MyPlugin struct {
pb.UnimplementedApiServer
m7s.Plugin
}
var _ = m7s.InstallPlugin[MyPlugin](
m7s.DefaultYaml(`your yaml config here`),
&pb.Api_ServiceDesc, // gRPC service descriptor
pb.RegisterApiHandler, // gRPC gateway handler
// ... other parameters
)
```
### 2. Proto File Specifications
All gRPC services must follow these Proto file specifications:
- Response structs must include code, message, and data fields
- Error handling should return errors directly, without manually setting code and message
- Run `sh scripts/protoc.sh` to generate pb files after modifying global.proto
- Run `sh scripts/protoc.sh {pluginName}` to generate corresponding pb files after modifying plugin-related proto files
## Service Implementation Mechanism
### 1. Server Configuration
gRPC services use port settings from the global TCP configuration:
```yaml
global:
tcp:
listenaddr: :8080 # gRPC service listen address and port
listentls: :8443 # gRPC TLS service listen address and port (if enabled)
```
Configuration items include:
- Listen address and port settings (specified in global TCP configuration)
- TLS/SSL certificate configuration (if enabled)
### 2. Error Handling
Error handling follows these principles:
- Return errors directly, no need to manually set code and message
- The system automatically handles errors and sets response format
## Best Practices
1. Service Definition
- Clear service interface design
- Appropriate method naming
- Complete interface documentation
2. Performance Optimization
- Use streaming for large data
- Set reasonable timeout values
3. Security Considerations
- Enable TLS encryption as needed
- Implement necessary access controls

145
doc/arch/http.md Normal file
View File

@@ -0,0 +1,145 @@
# HTTP Service Mechanism
Monibuca provides comprehensive HTTP service support, including RESTful API, WebSocket, HTTP-FLV, and other protocols. This document details the implementation mechanism and usage of the HTTP service.
## HTTP Configuration
### 1. Configuration Priority
- Plugin HTTP configuration takes precedence over global HTTP configuration
- If a plugin doesn't have HTTP configuration, global HTTP configuration is used
### 2. Configuration Items
```yaml
# Global configuration example
global:
http:
listenaddr: :8080 # Listen address and port
listentlsaddr: :8081 # TLS listen address and port
certfile: "" # SSL certificate file path
keyfile: "" # SSL key file path
cors: true # Whether to allow CORS
username: "" # Basic auth username
password: "" # Basic auth password
# Plugin configuration example (takes precedence over global config)
plugin_name:
http:
listenaddr: :8081
cors: false
username: "admin"
password: "123456"
```
## Service Processing Flow
### 1. Request Processing Order
When the HTTP server receives a request, it processes it in the following order:
1. First attempts to forward to the corresponding gRPC service
2. If no corresponding gRPC service is found, looks for plugin-registered HTTP handlers
3. If nothing is found, returns a 404 error
### 2. Handler Registration Methods
Plugins can register HTTP handlers in two ways:
1. Reflection Registration: The system automatically obtains plugin handling methods through reflection
- Method names must start with uppercase to be reflected (Go language rule)
- Usually use `API_` as method name prefix (recommended but not mandatory)
- Method signature must be `func(w http.ResponseWriter, r *http.Request)`
- URL path auto-generation rules:
- Underscores `_` in method names are converted to slashes `/`
- Example: `API_relay_` method maps to `/API/relay/*` path
- If a method name ends with underscore, it indicates a wildcard path that matches any subsequent path
2. Manual Registration: Plugin implements `IRegisterHandler` interface for manual registration
- Lowercase methods can't be reflected, must be registered manually
- Manual registration can use path parameters (like `:id`)
- More flexible routing rule configuration
Example code:
```go
// Reflection registration example
type YourPlugin struct {
// ...
}
// Uppercase start, can be reflected
// Automatically maps to /API/relay/*
func (p *YourPlugin) API_relay_(w http.ResponseWriter, r *http.Request) {
// Handle wildcard path requests
}
// Lowercase start, can't be reflected, needs manual registration
func (p *YourPlugin) handleUserRequest(w http.ResponseWriter, r *http.Request) {
// Handle parameterized requests
}
// Manual registration example
func (p *YourPlugin) RegisterHandler() {
// Can use path parameters
engine.GET("/api/user/:id", p.handleUserRequest)
}
```
## Middleware Mechanism
### 1. Adding Middleware
Plugins can add global middleware using the `AddMiddleware` method to handle all HTTP requests. Middleware executes in the order it was added.
Example code:
```go
func (p *YourPlugin) OnInit() {
// Add authentication middleware
p.GetCommonConf().AddMiddleware(func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Execute before request handling
if !authenticate(r) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Call next handler
next(w, r)
// Execute after request handling
}
})
}
```
### 2. Middleware Use Cases
- Authentication and Authorization
- Request Logging
- CORS Handling
- Request Rate Limiting
- Response Header Setting
- Error Handling
- Performance Monitoring
## Special Protocol Support
### 1. HTTP-FLV
- Supports HTTP-FLV live stream distribution
- Automatically generates FLV headers
- Supports GOP caching
- Supports WebSocket-FLV
### 2. HTTP-MP4
- Supports HTTP-MP4 stream distribution
- Supports fMP4 file distribution
### 3. HLS
- Supports HLS protocol
- Supports MPEG-TS encapsulation
### 4. WebSocket
- Supports custom message protocols
- Supports ws-flv
- Supports ws-mp4

57
doc/arch/index.md Normal file
View File

@@ -0,0 +1,57 @@
# Architecture Design
## Directory Structure
[catalog.md](./catalog.md)
## Audio/Video Streaming System
### Relay Mechanism
[relay.md](./relay.md)
### Alias Mechanism
[alias.md](./alias.md)
### Authentication Mechanism
[auth.md](./auth.md)
## Plugin System
### Lifecycle
[plugin.md](./plugin.md)
### Plugin Development
[plugin/README.md](../plugin/README.md)
## Task System
[task.md](./task.md)
## Configuration Mechanism
[config.md](./config.md)
## Logging System
[log.md](./log.md)
## Database Mechanism
[db.md](./db.md)
## GRPC Service
[grpc.md](./grpc.md)
## HTTP Service
[http.md](./http.md)
## Admin Service
[admin.md](./admin.md)

124
doc/arch/log.md Normal file
View File

@@ -0,0 +1,124 @@
# Logging Mechanism
Monibuca uses Go's standard library `slog` as its logging system, providing structured logging functionality.
## Log Configuration
In the global configuration, you can set the log level through the `LogLevel` field. Supported log levels are:
- trace
- debug
- info
- warn
- error
Configuration example:
```yaml
global:
LogLevel: "debug" # Set log level to debug
```
## Log Format
The default log format includes the following information:
- Timestamp (format: HH:MM:SS.MICROSECONDS)
- Log level
- Log message
- Structured fields
Example output:
```
15:04:05.123456 INFO server started
15:04:05.123456 ERROR failed to connect database dsn="xxx" type="mysql"
```
## Log Handlers
Monibuca uses `console-slog` as the default log handler, which provides:
1. Color output support
2. Microsecond-level timestamps
3. Structured field formatting
### Multiple Handler Support
Monibuca implements a `MultiLogHandler` mechanism, supporting multiple log handlers simultaneously. This provides the following advantages:
1. Can output logs to multiple targets simultaneously (e.g., console, file, log service)
2. Supports dynamic addition and removal of log handlers
3. Each handler can have its own log level settings
4. Supports log grouping and property inheritance
Through the plugin system, various logging methods can be extended, for example:
- LogRotate plugin: Supports log file rotation
- VMLog plugin: Supports storing logs in VictoriaMetrics time-series database
## Using Logs in Plugins
Each plugin inherits the server's log configuration. Plugins can log using the following methods:
```go
plugin.Info("message", "key1", value1, "key2", value2) // Log INFO level
plugin.Debug("message", "key1", value1) // Log DEBUG level
plugin.Warn("message", "key1", value1) // Log WARN level
plugin.Error("message", "key1", value1) // Log ERROR level
```
## Log Initialization Process
1. Create default console log handler at server startup
2. Read log level settings from configuration file
3. Apply log level configuration
4. Set inherited log configuration for each plugin
## Best Practices
1. Use Log Levels Appropriately
- trace: For most detailed tracing information
- debug: For debugging information
- info: For important information during normal operation
- warn: For warning information
- error: For error information
2. Use Structured Fields
- Avoid concatenating variables in messages
- Use key-value pairs to record additional information
3. Error Handling
- Include complete error information when logging errors
- Add relevant context information
Example:
```go
// Recommended
s.Error("failed to connect database", "error", err, "dsn", dsn)
// Not recommended
s.Error("failed to connect database: " + err.Error())
```
## Extending the Logging System
To extend the logging system, you can:
1. Implement custom `slog.Handler` interface
2. Use `LogHandler.Add()` method to add new handlers
3. Provide more complex logging functionality through the plugin system
Example of adding a custom log handler:
```go
type MyLogHandler struct {
slog.Handler
}
// Add handler during plugin initialization
func (p *MyPlugin) OnInit() error {
handler := &MyLogHandler{}
p.Server.LogHandler.Add(handler)
return nil
}
```

170
doc/arch/plugin.md Normal file
View File

@@ -0,0 +1,170 @@
# Plugin System
Monibuca adopts a plugin-based architecture design, extending functionality through its plugin mechanism. The plugin system is one of Monibuca's core features, allowing developers to add new functionality in a modular way without modifying the core code.
## Plugin Lifecycle
The plugin system has complete lifecycle management, including the following phases:
### 1. Registration Phase
Plugins are registered using the `InstallPlugin` generic function, during which:
- Plugin metadata (PluginMeta) is created, including:
- Plugin name: automatically extracted from the plugin struct name (removing "Plugin" suffix)
- Plugin version: extracted from the caller's file path or package path, defaults to "dev" if not extractable
- Plugin type: obtained through reflection of the plugin struct type
- Optional features are registered:
- Exit handler (OnExitHandler)
- Default configuration (DefaultYaml)
- Puller
- Pusher
- Recorder
- Transformer
- Publish authentication (AuthPublisher)
- Subscribe authentication (AuthSubscriber)
- gRPC service (ServiceDesc)
- gRPC gateway handler (RegisterGRPCHandler)
- Plugin metadata is added to the global plugin list
The registration phase is the first stage in a plugin's lifecycle, providing the plugin system with basic information and functional definitions, preparing for subsequent initialization and startup.
### 2. Initialization Phase (Init)
Plugins are initialized through the `Plugin.Init` method, including these steps:
1. Instance Verification
- Check if the plugin implements the IPlugin interface
- Get plugin instance through reflection
2. Basic Setup
- Set plugin metadata and server reference
- Configure plugin logger
- Set plugin name and version information
3. Environment Check
- Check if plugin is disabled by environment variables ({PLUGIN_NAME}_ENABLE=false)
- Check global disable status (DisableAll)
- Check enable status in user configuration (enable)
4. Configuration Loading
- Parse common configuration
- Load default YAML configuration
- Merge user configuration
- Apply final configuration and log
5. Database Initialization (if needed)
- Check database connection configuration (DSN)
- Establish database connection
- Auto-migrate database tables (for recording functionality)
6. Status Recording
- Record plugin version
- Record user configuration
- Set log level
- Record initialization status
If errors occur during initialization:
- Plugin is marked as disabled
- Disable reason is recorded
- Plugin is added to the disabled plugins list
The initialization phase prepares necessary environment and resources for plugin operation, crucial for ensuring normal plugin operation.
### 3. Startup Phase (Start)
Plugins start through the `Plugin.Start` method, executing these operations in sequence:
1. gRPC Service Registration (if configured)
- Register gRPC service
- Register gRPC gateway handler
- Handle gRPC-related errors
2. Plugin Management
- Add plugin to server's plugin list
- Set plugin status to running
3. Network Listener Initialization
- Start HTTP/HTTPS services
- Start TCP/TLS services (if implementing ITCPPlugin interface)
- Start UDP services (if implementing IUDPPlugin interface)
- Start QUIC services (if implementing IQUICPlugin interface)
4. Plugin Initialization Callback
- Call plugin's OnInit method
- Handle initialization errors
5. Timer Task Setup
- Configure server keepalive task (if enabled)
- Set up other timer tasks
If errors occur during startup:
- Error reason is recorded
- Plugin is marked as disabled
- Subsequent startup steps are stopped
The startup phase is crucial for plugins to begin providing services, with all preparations completed and ready for business logic processing.
### 4. Stop Phase (Stop)
The plugin stop phase is implemented through the `Plugin.OnStop` method and related stop handling logic, including:
1. Service Shutdown
- Stop all network services (HTTP/HTTPS/TCP/UDP/QUIC)
- Close all network connections
- Stop processing new requests
2. Resource Cleanup
- Stop all timer tasks
- Close database connections (if any)
- Clean up temporary files and cache
3. Status Handling
- Update plugin status to stopped
- Remove from server's active plugin list
- Trigger stop event notifications
4. Callback Processing
- Call plugin's custom OnStop method
- Execute registered stop callback functions
- Handle errors during stop process
5. Connection Handling
- Wait for current request processing to complete
- Gracefully close existing connections
- Reject new connection requests
The stop phase aims to ensure plugins can safely and cleanly stop running without affecting other parts of the system.
### 5. Destroy Phase (Destroy)
The plugin destroy phase is implemented through the `Plugin.Dispose` method, the final phase in a plugin's lifecycle, including:
1. Resource Release
- Call plugin's OnStop method for stop processing
- Remove from server's plugin list
- Release all allocated system resources
2. Status Cleanup
- Clear all plugin status information
- Reset plugin internal variables
- Clear plugin configuration information
3. Connection Disconnection
- Disconnect all connections with other plugins
- Clean up plugin dependencies
- Remove event listeners
4. Data Cleanup
- Clean up temporary data generated by plugin
- Close and clean up database connections
- Delete unnecessary files
5. Final Processing
- Execute registered destroy callback functions
- Log destruction
- Ensure all resources are properly released
The destroy phase aims to ensure plugins completely clean up all resources, leaving no residual state, preventing memory and resource leaks.

45
doc/arch/relay.md Normal file
View File

@@ -0,0 +1,45 @@
# Core Relay Process
## Publisher
A Publisher is an object that writes audio/video data to the RingBuffer on the server. It exposes WriteVideo and WriteAudio methods.
When writing through WriteVideo and WriteAudio, it creates Tracks, parses data, and generates ICodecCtx. To start publishing, simply call the Plugin's Publish method.
### Accepting Stream Push
Plugins like rtmp, rtsp listen on a port to accept stream pushes.
### Pulling Streams from Remote
- Plugins that implement OnPullProxyAdd method can pull streams from remote sources.
- Plugins that inherit from HTTPFilePuller can pull streams from http or files.
### Pulling from Local Recording Files
Plugins that inherit from RecordFilePuller can pull streams from local recording files.
## Subscriber
A Subscriber is an object that reads audio/video data from the RingBuffer. Subscribing to a stream involves two steps:
1. Call the Plugin's Subscribe method, passing StreamPath and Subscribe configuration.
2. Call the PlayBlock method to start reading data, which blocks until the subscription ends.
The reason for splitting into two steps is that the first step might fail (timeout etc.), or might need some interaction work after the first step succeeds.
The first step will block for some time, waiting for the publisher (if there's no publisher initially) and waiting for the publisher's tracks to be created.
### Accepting Stream Pull
For example, rtmp and rtsp plugins listen on a port to accept playback requests.
### Pushing to Remote
- Plugins that implement OnPushProxyAdd method can push streams to remote destinations.
### Writing to Local Files
Plugins with recording functionality need to subscribe to the stream before writing to local files.
## On-Demand Pull (Publishing)
Triggered by subscribers, when calling plugin's OnSubscribe, it notifies all plugins of a subscription demand, at which point plugins can respond to this demand by publishing a stream. For example, pulling recording streams falls into this category. It's crucial to configure using regular expressions to prevent simultaneous publishing.

101
doc/arch/task.md Normal file
View File

@@ -0,0 +1,101 @@
# Task Mechanism
The task mechanism permeates the entire project, defined in the /pkg/task directory. When designing any logic, you must first consider implementing it using the task mechanism, which ensures observability and panic capture, among other benefits.
## Concept Definitions
### Inheritance
In the task mechanism, all tasks are implemented through inheritance.
While Go doesn't have inheritance, it can be achieved through embedding.
### Macro Task
A macro task, also called a parent task, can contain multiple child tasks and is itself a task.
### Child Task Goroutine
Each macro task starts a goroutine to execute the Start, Run, and Dispose methods of child tasks. Therefore, child tasks sharing the same parent task can avoid concurrent execution issues. This goroutine might not be created immediately, implementing lazy loading.
## Task Definition
Tasks are typically defined by inheriting from `task.Task`, `task.Job`, `task.Work`, `task.ChannelTask`, or `task.TickTask`.
For example:
```go
type MyTask struct {
task.Task
}
```
- `task.Task` is the base class for all tasks, defining basic task properties and methods.
- `task.Job` can contain child tasks and ends when all child tasks complete.
- `task.Work` similar to Job, but continues executing after child tasks complete.
- `task.ChannelTask` custom signal task, implemented by overriding the `GetSignal` method.
- `task.TickTask` timer task, inherits from `task.ChannelTask`, controls timer interval by overriding `GetTickInterval` method.
### Defining Task Start Method
Implement task startup by defining a Start() error method.
The returned error indicates whether the task started successfully. Nil indicates successful startup, otherwise indicates startup failure (special case: returning Complete indicates task completion).
Start typically includes resource creation, such as opening files, establishing network connections, etc.
The Start method is optional; if not defined, startup is considered successful by default.
### Defining Task Execution Process
Implement task execution process by defining a Run() error method.
This method typically executes time-consuming operations and blocks the parent task's child task goroutine.
There's also a non-blocking way to run time-consuming operations by defining a Go() error method.
A nil error return indicates successful execution, otherwise indicates execution failure (special case: returning Complete indicates task completion).
Run and Go are optional; if not defined, the task remains in running state.
### Defining Task Destruction Process
Implement task destruction process by defining a Dispose() method.
This method typically releases resources, such as closing files, network connections, etc.
The Dispose method is optional; if not defined, no action is taken when the task ends.
## Hook Mechanism
Implement hooks through OnStart, OnBeforeDispose, and OnDispose methods.
## Waiting for Task Start and End
Implement waiting for task start and end through WaitStarted() and WaitStopped() methods. This approach blocks the current goroutine.
## Retry Mechanism
Implement retry mechanism by setting Task's RetryCount and RetryInterval. There's a setting method, SetRetry(maxRetry int, retryInterval time.Duration).
### Trigger Conditions
- When Start fails, it retries calling Start until successful.
- When Run or Go fails, it calls Dispose to release resources before calling Start to begin the retry process.
### Termination Conditions
- Retries stop when the retry count is exhausted.
- Retries stop when Start, Run, or Go returns ErrStopByUser, ErrExit, or ErrTaskComplete.
## Starting a Task
Start a task by calling the parent task's AddTask method. Don't directly call a task's Start method. Start must be called by the parent task.
## Task Stopping
Implement task stopping through the Stop(err error) method. err cannot be nil. Don't override the Stop method when defining tasks.
## Task Stop Reason
Check task stop reason by calling the StopReason() method.
## Call Method
Calling a Job's Call method creates a temporary task to execute a function in the child task goroutine, typically used to access resources like maps that need protection from concurrent read/write. Since this function runs in the child task goroutine, it cannot call WaitStarted, WaitStopped, or other goroutine-blocking logic, as this would cause deadlock.

111
doc_CN/arch/admin.md Normal file
View File

@@ -0,0 +1,111 @@
# Admin 服务机制
Monibuca 提供了强大的管理服务支持,用于系统监控、配置管理、插件管理等管理功能。本文档详细说明了 Admin 服务的实现机制和使用方法。
## 服务架构
### 1. UI 界面
Admin 服务通过加载 `admin.zip` 文件来提供 Web 管理界面。该界面具有以下特点:
- 统一的管理界面入口
- 可调用所有服务器提供的 HTTP 接口
- 响应式设计,支持多种设备访问
- 模块化的功能组织
### 2. 配置管理
Admin 服务的配置位于全局配置global中的 admin 节,包括:
```yaml
admin:
enableLogin: false # 是否启用登录机制
filePath: admin.zip # 管理界面文件路径
homePage: home # 管理界面首页
users: # 用户列表(仅在启用登录机制时生效)
- username: admin # 用户名
password: admin # 密码
role: admin # 角色可选值admin、user
```
`enableLogin` 为 false 时,所有用户都以匿名用户身份访问。
当启用登录机制且数据库中没有用户时系统会自动创建一个默认管理员账户用户名admin密码admin
### 3. 认证机制
Admin 提供专门的用户登录验证接口,用于:
- 用户身份验证
- 访问令牌管理JWT
- 权限控制
- 会话管理
### 4. 接口规范
所有的 Admin API 都需要遵循以下规范:
- 响应格式统一包含 code、message、data 字段
- 成功响应使用 code = 0
- 错误处理采用统一的错误响应格式
- 必须进行权限验证
## 功能模块
### 1. 系统监控
- CPU 使用率监控
- 内存使用情况
- 网络带宽统计
- 磁盘使用情况
- 系统运行时间
- 在线用户统计
### 2. 插件管理
- 插件启用/禁用
- 插件配置修改
- 插件状态查看
- 插件版本管理
- 插件依赖检查
### 3. 流媒体管理
- 在线流列表查看
- 流状态监控
- 流控制(开始/停止)
- 流信息统计
- 录制管理
- 转码任务管理
## 安全机制
### 1. 认证机制
- JWT 令牌认证
- 会话超时控制
- IP 白名单控制
### 2. 权限控制
- 基于角色的访问控制RBAC
- 细粒度的权限管理
- 操作审计日志
- 敏感操作确认
## 最佳实践
1. 安全性
- 使用 HTTPS 加密
- 实施强密码策略
- 定期更新密钥
- 监控异常访问
2. 性能优化
- 合理的缓存策略
- 分页查询优化
- 异步处理耗时操作
3. 可维护性
- 完整的操作日志
- 清晰的错误提示
- 配置热更新

0
doc_CN/arch/auth.md Normal file
View File

77
doc_CN/arch/catalog.md Normal file
View File

@@ -0,0 +1,77 @@
# 目录结构说明
```bash
monibuca/
├── api.go # API接口定义
├── plugin.go # 插件系统核心实现
├── publisher.go # 发布者实现
├── subscriber.go # 订阅者实现
├── server.go # 服务器核心实现
├── puller.go # 拉流器实现
├── pusher.go # 推流器实现
├── pull-proxy.go # 拉流代理实现
├── push-proxy.go # 推流代理实现
├── recoder.go # 录制器实现
├── transformer.go # 转码器实现
├── wait-stream.go # 流等待实现
├── prometheus.go # Prometheus监控实现
├── pkg/ # 核心包
│ ├── auth/ # 认证相关
│ ├── codec/ # 编解码实现
│ ├── config/ # 配置相关
│ ├── db/ # 数据库相关
│ ├── task/ # 任务系统
│ ├── util/ # 工具函数
│ ├── filerotate/ # 文件轮转管理
│ ├── log.go # 日志实现
│ ├── raw.go # 原始数据处理
│ ├── error.go # 错误处理
│ ├── track.go # 媒体轨道实现
│ ├── track_test.go # 媒体轨道测试
│ ├── annexb.go # H.264/H.265 Annex-B格式处理
│ ├── av-reader.go # 音视频读取器
│ ├── avframe.go # 音视频帧结构
│ ├── ring-writer.go # 环形缓冲区写入器
│ ├── ring-reader.go # 环形缓冲区读取器
│ ├── adts.go # AAC-ADTS格式处理
│ ├── port.go # 端口管理
│ ├── ring_test.go # 环形缓冲区测试
│ └── event.go # 事件系统
├── plugin/ # 插件目录
│ ├── rtmp/ # RTMP协议插件
│ ├── rtsp/ # RTSP协议插件
│ ├── hls/ # HLS协议插件
│ ├── flv/ # FLV协议插件
│ ├── webrtc/ # WebRTC协议插件
│ ├── gb28181/ # GB28181协议插件
│ ├── onvif/ # ONVIF协议插件
│ ├── mp4/ # MP4相关插件
│ ├── room/ # 房间管理插件
│ ├── monitor/ # 监控插件
│ ├── rtp/ # RTP协议插件
│ ├── srt/ # SRT协议插件
│ ├── sei/ # SEI数据处理插件
│ ├── snap/ # 截图插件
│ ├── crypto/ # 加密插件
│ ├── debug/ # 调试插件
│ ├── cascade/ # 级联插件
│ ├── logrotate/ # 日志轮转插件
│ ├── stress/ # 压力测试插件
│ ├── vmlog/ # 虚拟内存日志插件
│ ├── preview/ # 预览插件
│ └── transcode/ # 转码插件
├── pb/ # Protocol Buffers定义和生成的代码
├── scripts/ # 脚本文件
├── doc/ # 英文文档
├── doc_CN/ # 中文文档
├── example/ # 示例代码
├── test/ # 测试代码
├── website/ # 网站前端代码
├── go.mod # Go模块定义
├── go.sum # Go依赖版本锁定
├── Dockerfile # Docker构建文件
└── README.md # 项目说明文档
```

291
doc_CN/arch/config.md Normal file
View File

@@ -0,0 +1,291 @@
# Monibuca 配置机制
Monibuca 采用灵活的配置机制,支持多种配置方式。配置文件采用 YAML 格式,可以通过文件或者直接传入配置对象的方式进行初始化。
## 配置加载流程
1. 配置初始化发生在 Server 启动阶段,通过以下三种方式之一提供配置:
- YAML 配置文件路径
- YAML 配置内容的字节数组
- 原始配置对象 (RawConfig)
2. 配置解析过程:
```go
// 支持三种配置输入方式
case string: // 配置文件路径
case []byte: // YAML 配置内容
case RawConfig: // 原始配置对象
```
## 配置结构
### 配置简化语法
当配置项的值是一个结构体或 map 类型时,系统支持一种简化的配置方式:如果直接配置一个简单类型的值,该值会被自动赋给结构体的第一个字段。
例如,对于以下结构体:
```go
type Config struct {
Port int
Host string
}
```
可以使用简化语法:
```yaml
plugin: 1935 # 等同于 plugin: { port: 1935 }
```
### 配置反序列化机制
每个插件都包含一个 `config.Config` 类型的字段,用于存储和管理配置信息。配置加载的优先级从高到低是:
1. 用户配置 (通过 `ParseUserFile`)
2. 默认配置 (通过 `ParseDefaultYaml`)
3. 全局配置 (通过 `ParseGlobal`)
4. 插件特定配置 (通过 `Parse`)
5. 通用配置 (通过 `Parse`)
配置会被自动反序列化到插件的公开属性中。例如:
```go
type MyPlugin struct {
Plugin
Port int `yaml:"port"`
Host string `yaml:"host"`
}
```
对应的 YAML 配置:
```yaml
myplugin:
port: 8080
host: "localhost"
```
配置会自动反序列化到 `Port` 和 `Host` 字段中。你可以通过 `Config` 提供的方法来查询配置:
- `Has(name string)` - 检查是否存在某个配置
- `Get(name string)` - 获取某个配置的值
- `GetMap()` - 获取所有配置的 map
此外,插件的配置支持保存修改:
```go
func (p *Plugin) SaveConfig() (err error)
```
这会将修改后的配置保存到 `{settingDir}/{pluginName}.yaml` 文件中。
### 全局配置
全局配置位于 YAML 文件的 `global` 节点下,包含以下主要配置项:
```yaml
global:
settingDir: ".m7s" # 设置文件目录
fatalDir: "fatal" # 错误日志目录
pulseInterval: "5s" # 心跳间隔
disableAll: false # 是否禁用所有插件
streamAlias: # 流别名配置
pattern: "target" # 正则表达式 -> 目标路径
location: # HTTP 路由转发规则
pattern: "target" # 正则表达式 -> 目标地址
admin: # 管理界面配置
enableLogin: false # 是否启用登录机制
filePath: "admin.zip" # 管理界面文件路径
homePage: "home" # 管理界面首页
users: # 用户列表(仅在启用登录时生效)
- username: "admin" # 用户名
password: "admin" # 密码
role: "admin" # 角色(admin/user)
```
### 数据库配置
如果配置了数据库连接,系统会自动进行以下操作:
1. 连接数据库
2. 自动迁移数据模型
3. 初始化用户数据(如果启用了登录机制)
4. 初始化代理配置
```yaml
global:
db:
dsn: "" # 数据库连接字符串
type: "" # 数据库类型
```
### 代理配置
系统支持拉流代理和推流代理配置:
```yaml
global:
pullProxy: # 拉流代理配置
- id: 1 # 代理ID
name: "proxy1" # 代理名称
url: "rtmp://..." # 代理地址
type: "rtmp" # 代理类型
pullOnStart: true # 是否启动时拉流
pushProxy: # 推流代理配置
- id: 1 # 代理ID
name: "proxy1" # 代理名称
url: "rtmp://..." # 代理地址
type: "rtmp" # 代理类型
pushOnStart: true # 是否启动时推流
audio: true # 是否推送音频
```
## 插件配置
每个插件可以有自己的配置节点,节点名为插件名称的小写形式:
```yaml
rtmp: # RTMP插件配置
port: 1935 # 监听端口
rtsp: # RTSP插件配置
port: 554 # 监听端口
```
## 配置优先级
配置系统采用多级优先级机制,从高到低依次为:
1. URL 查询参数配置 - 发布或订阅时通过 URL 查询参数指定的配置具有最高优先级
```
例如rtmp://localhost/live/stream?audio=false
```
2. 插件特定配置 - 在插件配置节点下的配置项
```yaml
rtmp:
publish:
audio: true
subscribe:
audio: true
```
3. 全局配置 - 在 global 节点下的配置项
```yaml
global:
publish:
audio: true
subscribe:
audio: true
```
## 通用配置
系统中存在一些通用配置项,这些配置项可以同时出现在全局配置和插件配置中。当插件使用这些配置项时,会优先使用插件配置中的值,如果插件配置中没有设置,则使用全局配置中的值。
主要的通用配置包括:
1. 发布配置Publish
```yaml
publish:
audio: true # 是否包含音频
video: true # 是否包含视频
bufferLength: 1000 # 缓冲长度
```
2. 订阅配置Subscribe
```yaml
subscribe:
audio: true # 是否订阅音频
video: true # 是否订阅视频
bufferLength: 1000 # 缓冲长度
```
3. HTTP 配置
```yaml
http:
listenAddr: ":8080" # 监听地址
```
4. 其他通用配置
- PublicIP - 公网 IP
- PublicIPv6 - 公网 IPv6
- LogLevel - 日志级别
- EnableAuth - 是否启用认证
使用示例:
```yaml
# 全局配置
global:
publish:
audio: true
video: true
subscribe:
audio: true
video: true
# 插件配置(优先级高于全局配置)
rtmp:
publish:
audio: false # 覆盖全局配置
subscribe:
video: false # 覆盖全局配置
# URL 查询参数(最高优先级)
# rtmp://localhost/live/stream?audio=true&video=false
```
## 配置热更新
目前系统支持管理界面文件admin.zip的热更新会定期检查文件变化并自动重新加载。
## 配置验证
系统在启动时会对配置进行基本验证:
1. 检查必要的目录权限
2. 验证数据库连接
3. 验证用户配置(如果启用登录机制)
## 配置示例
完整的配置文件示例:
```yaml
global:
settingDir: ".m7s"
fatalDir: "fatal"
pulseInterval: "5s"
disableAll: false
streamAlias:
"live/(.*)": "record/$1"
location:
"^/live/(.*)": "/hls/$1"
admin:
enableLogin: true
filePath: "admin.zip"
homePage: "home"
users:
- username: "admin"
password: "admin"
role: "admin"
db:
dsn: "host=localhost user=postgres password=postgres dbname=monibuca port=5432 sslmode=disable TimeZone=Asia/Shanghai"
type: "postgres"
pullProxy:
- id: 1
name: "proxy1"
url: "rtmp://example.com/live/stream"
type: "rtmp"
pullOnStart: true
pushProxy:
- id: 1
name: "proxy1"
url: "rtmp://example.com/live/stream"
type: "rtmp"
pushOnStart: true
audio: true
rtmp:
port: 1935
rtsp:
port: 554
```

94
doc_CN/arch/db.md Normal file
View File

@@ -0,0 +1,94 @@
# 数据库机制
Monibuca 提供了数据库支持功能,可以在全局配置和插件中分别配置和使用数据库。
## 配置说明
### 全局配置
在全局配置中可以通过以下字段配置数据库:
```yaml
global:
dsn: "数据库连接字符串"
dbType: "数据库类型"
```
### 插件配置
每个插件也可以单独配置数据库:
```yaml
pluginName:
dsn: "数据库连接字符串"
dbType: "数据库类型"
```
## 数据库初始化流程
### 全局数据库初始化
1. 服务器启动时,如果配置了 `dsn`,会尝试连接数据库
2. 连接成功后会自动迁移以下模型:
- User 用户表
- PullProxy 拉流代理表
- PushProxy 推流代理表
- StreamAliasDB 流别名表
3. 如果开启了登录功能(`Admin.EnableLogin = true`),会根据配置文件创建或更新用户
4. 如果数据库中没有任何用户,会创建一个默认的管理员账户:
- 用户名: admin
- 密码: admin
- 角色: admin
### 插件数据库初始化
1. 插件初始化时会检查插件配置中的 `dsn`
2. 如果插件配置的 `dsn` 与全局配置相同,则直接使用全局数据库连接
3. 如果插件配置了不同的 `dsn`,则会创建新的数据库连接
4. 如果插件实现了 Recorder 接口,会自动迁移 RecordStream 表
## 数据库使用
### 全局数据库访问
可以通过 Server 实例访问全局数据库:
```go
server.DB
```
### 插件数据库访问
插件可以通过自身实例访问数据库:
```go
plugin.DB
```
## 注意事项
1. 数据库连接失败会导致相应的功能被禁用
2. 插件使用独立数据库时需要自行管理数据库连接
3. 数据库迁移失败会导致插件被禁用
4. 建议在可能的情况下复用全局数据库连接,避免创建过多连接
## 内置数据表
### User 表
用于存储用户信息,包含以下字段:
- Username: 用户名
- Password: 密码
- Role: 角色(admin/user)
### PullProxy 表
用于存储拉流代理配置
### PushProxy 表
用于存储推流代理配置
### StreamAliasDB 表
用于存储流别名配置
### RecordStream 表
用于存储录制相关信息(仅在插件实现 Recorder 接口时创建)

72
doc_CN/arch/grpc.md Normal file
View File

@@ -0,0 +1,72 @@
# GRPC 服务机制
Monibuca 提供了 gRPC 服务支持,允许插件通过 gRPC 协议提供服务。本文档说明了 gRPC 服务的实现机制和使用方法。
## 服务注册机制
### 1. 服务注册
插件注册 gRPC 服务需要在 `InstallPlugin` 时传入 ServiceDesc 和 Handler
```go
// 示例:在插件中注册 gRPC 服务
type MyPlugin struct {
pb.UnimplementedApiServer
m7s.Plugin
}
var _ = m7s.InstallPlugin[MyPlugin](
m7s.DefaultYaml(`your yaml config here`),
&pb.Api_ServiceDesc, // gRPC service descriptor
pb.RegisterApiHandler, // gRPC gateway handler
// ... 其他参数
)
```
### 2. Proto 文件规范
所有的 gRPC 服务都需要遵循以下 Proto 文件规范:
- 响应结构体必须包含 code、message、data 字段
- 错误处理采用直接返回 error 的方式,无需手动设置 code 和 message
- 修改 global.proto 后需要运行 `sh scripts/protoc.sh` 生成 pb 文件
- 修改插件相关的 proto 文件后需要运行 `sh scripts/protoc.sh {pluginName}` 生成对应的 pb 文件
## 服务实现机制
### 1. 服务器配置
gRPC 服务使用全局 TCP 配置中的端口设置:
```yaml
global:
tcp:
listenaddr: :8080 # gRPC 服务监听地址和端口
listentls: :8443 # gRPC TLS 服务监听地址和端口(如果启用)
```
配置项包括:
- 监听地址和端口设置(在全局 TCP 配置中指定)
- TLS/SSL 证书配置(如果启用)
### 2. 错误处理
错误处理遵循以下原则:
- 直接返回 error无需手动设置 code 和 message
- 系统会自动处理错误并设置响应格式
## 最佳实践
1. 服务定义
- 清晰的服务接口设计
- 合理的方法命名
- 完整的接口文档
2. 性能优化
- 使用流式处理大数据
- 合理设置超时时间
3. 安全考虑
- 根据需要启用 TLS 加密
- 实现必要的访问控制

145
doc_CN/arch/http.md Normal file
View File

@@ -0,0 +1,145 @@
# HTTP 服务机制
Monibuca 提供了完整的 HTTP 服务支持,包括 RESTful API、WebSocket、HTTP-FLV 等多种协议支持。本文档详细说明了 HTTP 服务的实现机制和使用方法。
## HTTP 配置
### 1. 配置优先级
- 插件的 HTTP 配置优先于全局 HTTP 配置
- 如果插件没有配置 HTTP则使用全局 HTTP 配置
### 2. 配置项说明
```yaml
# 全局配置示例
global:
http:
listenaddr: :8080 # 监听地址和端口
listentlsaddr: :8081 # 监听tls地址和端口
certfile: "" # SSL证书文件路径
keyfile: "" # SSL密钥文件路径
cors: true # 是否允许跨域
username: "" # Basic认证用户名
password: "" # Basic认证密码
# 插件配置示例(优先于全局配置)
plugin_name:
http:
listenaddr: :8081
cors: false
username: "admin"
password: "123456"
```
## 服务处理流程
### 1. 请求处理顺序
HTTP 服务器接收到请求后,按以下顺序处理:
1. 首先尝试转发到对应的 gRPC 服务
2. 如果没有找到对应的 gRPC 服务,则查找插件注册的 HTTP handler
3. 如果都没有找到,返回 404 错误
### 2. Handler 注册方式
插件可以通过以下两种方式注册 HTTP handler
1. 反射注册:系统自动通过反射获取插件的处理方法
- 方法名必须大写开头才能被反射获取Go 语言规则)
- 通常使用 `API_` 作为方法名前缀(推荐但不强制)
- 方法签名必须为 `func(w http.ResponseWriter, r *http.Request)`
- URL 路径自动生成规则:
- 方法名中的下划线 `_` 会被转换为斜杠 `/`
- 例如:`API_relay_` 方法将映射到 `/API/relay/*` 路径
- 如果方法名以下划线结尾,表示这是一个通配符路径,可以匹配后续任意路径
2. 手动注册:插件实现 `IRegisterHandler` 接口进行手动注册
- 小写开头的方法无法被反射获取,需要通过手动注册方式
- 手动注册可以使用路径参数(如 `:id`
- 更灵活的路由规则配置
示例代码:
```go
// 反射注册示例
type YourPlugin struct {
// ...
}
// 大写开头,可以被反射获取
// 自动映射到 /API/relay/*
func (p *YourPlugin) API_relay_(w http.ResponseWriter, r *http.Request) {
// 处理通配符路径的请求
}
// 小写开头,无法被反射获取,需要手动注册
func (p *YourPlugin) handleUserRequest(w http.ResponseWriter, r *http.Request) {
// 处理带参数的请求
}
// 手动注册示例
func (p *YourPlugin) RegisterHandler() {
// 可以使用路径参数
engine.GET("/api/user/:id", p.handleUserRequest)
}
```
## 中间件机制
### 1. 添加中间件
插件可以通过 `AddMiddleware` 方法添加全局中间件,用于处理所有 HTTP 请求。中间件按照添加顺序依次执行。
示例代码:
```go
func (p *YourPlugin) OnInit() {
// 添加认证中间件
p.GetCommonConf().AddMiddleware(func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 在请求处理前执行
if !authenticate(r) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 调用下一个处理器
next(w, r)
// 在请求处理后执行
}
})
}
```
### 2. 中间件使用场景
- 认证和授权
- 请求日志记录
- CORS 处理
- 请求限流
- 响应头设置
- 错误处理
- 性能监控
## 特殊协议支持
### 1. HTTP-FLV
- 支持 HTTP-FLV 直播流分发
- 自动生成 FLV 头
- 支持 GOP 缓存
- 支持 WebSocket-FLV
### 2. HTTP-MP4
- 支持 HTTP-MP4 流分发
- 支持 fMP4 文件分发
### 3. HLS
- 支持 HLS 协议
- 支持 MPEG-TS 封装
### 4. WebSocket
- 支持自定义消息协议
- 支持ws-flv
- 支持ws-mp4

57
doc_CN/arch/index.md Normal file
View File

@@ -0,0 +1,57 @@
# 架构设计
## 目录结构
[catalog.md](./catalog.md)
## 音视频流系统
### 转发机制
[relay.md](./relay.md)
### 别名机制
[alias.md](./alias.md)
### 鉴权机制
[auth.md](./auth.md)
## 插件系统
### 生命周期
[plugin.md](./plugin.md)
### 开发插件
[plugin/README_CN.md](../plugin/README_CN.md)
## 任务系统
[task.md](./task.md)
## 配置机制
[config.md](./config.md)
## 日志系统
[log.md](./log.md)
## 数据库机制
[db.md](./db.md)
## GRPC 服务
[grpc.md](./grpc.md)
## HTTP 服务
[http.md](./http.md)
## Admin 服务
[admin.md](./admin.md)

124
doc_CN/arch/log.md Normal file
View File

@@ -0,0 +1,124 @@
# 日志机制
Monibuca 使用 Go 标准库的 `slog` 作为日志系统,提供了结构化的日志记录功能。
## 日志配置
在全局配置中,可以通过 `LogLevel` 字段来设置日志级别。支持的日志级别有:
- trace
- debug
- info
- warn
- error
配置示例:
```yaml
global:
LogLevel: "debug" # 设置日志级别为 debug
```
## 日志格式
默认的日志格式包含以下信息:
- 时间戳 (格式: HH:MM:SS.MICROSECONDS)
- 日志级别
- 日志消息
- 结构化字段
示例输出:
```
15:04:05.123456 INFO server started
15:04:05.123456 ERROR failed to connect database dsn="xxx" type="mysql"
```
## 日志处理器
Monibuca 使用 `console-slog` 作为默认的日志处理器,它提供了:
1. 彩色输出支持
2. 微秒级时间戳
3. 结构化字段格式化
### 多处理器支持
Monibuca 实现了 `MultiLogHandler` 机制,支持同时使用多个日志处理器。这提供了以下优势:
1. 可以同时将日志输出到多个目标(如控制台、文件、日志服务等)
2. 支持动态添加和移除日志处理器
3. 每个处理器可以有自己的日志级别设置
4. 支持日志分组和属性继承
通过插件系统,可以扩展多种日志处理方式,例如:
- LogRotate 插件:支持日志文件轮转
- VMLog 插件:支持将日志存储到 VictoriaMetrics 时序数据库
## 在插件中使用日志
每个插件都会继承服务器的日志配置。插件可以通过以下方式记录日志:
```go
plugin.Info("message", "key1", value1, "key2", value2) // 记录 INFO 级别日志
plugin.Debug("message", "key1", value1) // 记录 DEBUG 级别日志
plugin.Warn("message", "key1", value1) // 记录 WARN 级别日志
plugin.Error("message", "key1", value1) // 记录 ERROR 级别日志
```
## 日志初始化流程
1. 服务器启动时创建默认的控制台日志处理器
2. 从配置文件读取日志级别设置
3. 应用日志级别配置
4. 为每个插件设置继承的日志配置
## 最佳实践
1. 合理使用日志级别
- trace: 用于最详细的追踪信息
- debug: 用于调试信息
- info: 用于正常运行时的重要信息
- warn: 用于警告信息
- error: 用于错误信息
2. 使用结构化字段
- 避免在消息中拼接变量
- 使用 key-value 对记录额外信息
3. 错误处理
- 记录错误时包含完整的错误信息
- 添加相关的上下文信息
示例:
```go
// 推荐
s.Error("failed to connect database", "error", err, "dsn", dsn)
// 不推荐
s.Error("failed to connect database: " + err.Error())
```
## 扩展日志系统
要扩展日志系统,可以通过以下方式:
1. 实现自定义的 `slog.Handler` 接口
2. 使用 `LogHandler.Add()` 方法添加新的处理器
3. 可以通过插件系统提供更复杂的日志功能
例如添加自定义日志处理器:
```go
type MyLogHandler struct {
slog.Handler
}
// 在插件初始化时添加处理器
func (p *MyPlugin) OnInit() error {
handler := &MyLogHandler{}
p.Server.LogHandler.Add(handler)
return nil
}
```

170
doc_CN/arch/plugin.md Normal file
View File

@@ -0,0 +1,170 @@
# 插件系统
Monibuca 采用插件化架构设计,通过插件机制来扩展功能。插件系统是 Monibuca 的核心特性之一,它允许开发者以模块化的方式添加新功能,而不需要修改核心代码。
## 插件生命周期
插件系统具有完整的生命周期管理,主要包含以下阶段:
### 1. 注册阶段
插件通过 `InstallPlugin` 泛型函数进行注册,在此阶段会:
- 创建插件元数据(PluginMeta),包含:
- 插件名称:自动从插件结构体名称中提取(去除"Plugin"后缀)
- 插件版本:从调用者的文件路径或包路径中提取,如果无法提取则默认为"dev"
- 插件类型:通过反射获取插件结构体类型
- 注册可选功能:
- 退出处理器(OnExitHandler)
- 默认配置(DefaultYaml)
- 拉流器(Puller)
- 推流器(Pusher)
- 录制器(Recorder)
- 转换器(Transformer)
- 发布认证(AuthPublisher)
- 订阅认证(AuthSubscriber)
- gRPC服务(ServiceDesc)
- gRPC网关处理器(RegisterGRPCHandler)
- 将插件元数据添加到全局插件列表中
注册阶段是插件生命周期的第一个阶段,它为插件系统提供了插件的基本信息和功能定义,为后续的初始化和启动做准备。
### 2. 初始化阶段 (Init)
插件通过 `Plugin.Init` 方法进行初始化,此阶段包含以下步骤:
1. 实例化检查
- 检查插件是否实现了 IPlugin 接口
- 通过反射获取插件实例
2. 基础设置
- 设置插件元数据和服务器引用
- 配置插件日志记录器
- 设置插件名称和版本信息
3. 环境检查
- 检查环境变量是否禁用插件(通过 {PLUGIN_NAME}_ENABLE=false)
- 检查全局禁用状态(DisableAll)
- 检查用户配置中的启用状态(enable)
4. 配置加载
- 解析通用配置
- 加载默认YAML配置
- 合并用户配置
- 应用最终配置并记录
5. 数据库初始化(如果需要)
- 检查数据库连接配置(DSN)
- 建立数据库连接
- 自动迁移数据库表结构(针对录制功能)
6. 状态记录
- 记录插件版本
- 记录用户配置
- 设置日志级别
- 记录初始化状态
如果在初始化过程中发生错误:
- 插件将被标记为禁用状态
- 记录禁用原因
- 添加到已禁用插件列表
初始化阶段为插件的运行准备必要的环境和资源,是确保插件正常运行的关键阶段。
### 3. 启动阶段 (Start)
插件通过 `Plugin.Start` 方法启动,此阶段按顺序执行以下操作:
1. gRPC服务注册如果配置
- 注册gRPC服务
- 注册gRPC网关处理器
- 处理gRPC相关错误
2. 插件管理
- 将插件添加到服务器的插件列表中
- 设置插件状态为运行中
3. 网络监听初始化
- HTTP/HTTPS服务启动
- TCP/TLS服务启动如果实现了ITCPPlugin接口
- UDP服务启动如果实现了IUDPPlugin接口
- QUIC服务启动如果实现了IQUICPlugin接口
4. 插件初始化回调
- 调用插件的OnInit方法
- 处理初始化错误
5. 定时任务设置
- 配置服务器保活任务(如果启用)
- 设置其他定时任务
如果在启动过程中发生错误:
- 记录错误原因
- 将插件标记为禁用状态
- 停止后续启动步骤
启动阶段是插件开始提供服务的关键阶段,此时插件完成了所有准备工作,可以开始处理业务逻辑。
### 4. 停止阶段 (Stop)
插件的停止阶段通过 `Plugin.OnStop` 方法和相关的停止处理逻辑实现,主要包含以下步骤:
1. 停止服务
- 停止所有网络服务HTTP/HTTPS/TCP/UDP/QUIC
- 关闭所有网络连接
- 停止处理新的请求
2. 资源清理
- 停止所有定时任务
- 关闭数据库连接(如果有)
- 清理临时文件和缓存
3. 状态处理
- 更新插件状态为已停止
- 从服务器的活动插件列表中移除
- 触发停止事件通知
4. 回调处理
- 调用插件自定义的OnStop方法
- 执行注册的停止回调函数
- 处理停止过程中的错误
5. 连接处理
- 等待当前请求处理完成
- 优雅关闭现有连接
- 拒绝新的连接请求
停止阶段的主要目标是确保插件能够安全、干净地停止运行,不影响系统的其他部分。
### 5. 销毁阶段 (Destroy)
插件的销毁阶段通过 `Plugin.Dispose` 方法实现,这是插件生命周期的最后阶段,主要包含以下步骤:
1. 资源释放
- 调用插件的OnStop方法进行停止处理
- 从服务器的插件列表中移除
- 释放所有分配的系统资源
2. 状态清理
- 清除插件的所有状态信息
- 重置插件的内部变量
- 清空插件的配置信息
3. 连接断开
- 断开与其他插件的所有连接
- 清理插件间的依赖关系
- 移除事件监听器
4. 数据清理
- 清理插件产生的临时数据
- 关闭并清理数据库连接
- 删除不再需要的文件
5. 最终处理
- 执行注册的销毁回调函数
- 记录销毁日志
- 确保所有资源都被正确释放
销毁阶段的主要目标是确保插件完全清理所有资源,不留下任何残留状态,防止内存泄漏和资源泄露。

View File

@@ -134,89 +134,105 @@ func (conf *WebRTCPlugin) Batch(w http.ResponseWriter, r *http.Request) {
}
// Create data channel for signaling
dataChannel, err := conn.PeerConnection.CreateDataChannel("signal", nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
conn.PeerConnection.OnDataChannel(func(dataChannel *DataChannel) {
conf.Debug("data channel created", "label", dataChannel.Label())
dataChannel.OnMessage(func(msg DataChannelMessage) {
var signal Signal
if err := json.Unmarshal(msg.Data, &signal); err != nil {
conf.Error("failed to unmarshal signal", "error", err)
return
}
switch signal.Type {
case SignalTypePublish:
if publisher, err := conf.Publish(conf.Context, signal.StreamPath); err == nil {
conn.Publisher = publisher
conn.Publisher.RemoteAddr = r.RemoteAddr
conn.Receive()
// Renegotiate SDP after successful publish
if answer, err := conn.GetAnswer(); err == nil {
dataChannel.SendText(NewAnswerSingal(answer.SDP))
} else {
dataChannel.SendText(NewErrorSignal(err.Error(), signal.StreamPath))
}
} else {
dataChannel.SendText(NewErrorSignal(err.Error(), signal.StreamPath))
}
case SignalTypeSubscribe:
if err := conn.SetRemoteDescription(SessionDescription{
Type: SDPTypeOffer,
SDP: signal.Offer,
}); err != nil {
dataChannel.SendText(NewErrorSignal("Failed to set remote description: "+err.Error(), ""))
dataChannel.OnMessage(func(msg DataChannelMessage) {
conf.Debug("received data channel message", "length", len(msg.Data), "is_string", msg.IsString)
var signal Signal
if err := json.Unmarshal(msg.Data, &signal); err != nil {
conf.Error("failed to unmarshal signal", "error", err)
return
}
// First remove subscribers that are not in the new list
for streamPath := range conn.Subscribers {
found := false
for _, newPath := range signal.StreamList {
if streamPath == newPath {
found = true
break
conf.Debug("signal received", "type", signal.Type, "stream_path", signal.StreamPath)
switch signal.Type {
case SignalTypePublish:
if publisher, err := conf.Publish(conf.Context, signal.StreamPath); err == nil {
conn.Publisher = publisher
conn.Publisher.RemoteAddr = r.RemoteAddr
conn.Receive()
// Renegotiate SDP after successful publish
if answer, err := conn.GetAnswer(); err == nil {
answerSignal := NewAnswerSingal(answer.SDP)
conf.Debug("sending answer signal", "stream_path", signal.StreamPath)
dataChannel.SendText(answerSignal)
} else {
errSignal := NewErrorSignal(err.Error(), signal.StreamPath)
conf.Debug("sending error signal", "error", err.Error(), "stream_path", signal.StreamPath)
dataChannel.SendText(errSignal)
}
} else {
errSignal := NewErrorSignal(err.Error(), signal.StreamPath)
conf.Debug("sending error signal", "error", err.Error(), "stream_path", signal.StreamPath)
dataChannel.SendText(errSignal)
}
case SignalTypeSubscribe:
if err := conn.SetRemoteDescription(SessionDescription{
Type: SDPTypeOffer,
SDP: signal.Offer,
}); err != nil {
errSignal := NewErrorSignal("Failed to set remote description: "+err.Error(), "")
conf.Debug("sending error signal", "error", err.Error())
dataChannel.SendText(errSignal)
return
}
// First remove subscribers that are not in the new list
for streamPath := range conn.Subscribers {
found := false
for _, newPath := range signal.StreamList {
if streamPath == newPath {
found = true
break
}
}
if !found {
conn.RemoveSubscriber(streamPath)
}
}
if !found {
conn.RemoveSubscriber(streamPath)
// Then add new subscribers
for _, streamPath := range signal.StreamList {
// Skip if already subscribed
if conn.HasSubscriber(streamPath) {
continue
}
if subscriber, err := conf.Subscribe(conf.Context, streamPath); err == nil {
subscriber.RemoteAddr = r.RemoteAddr
conn.AddSubscriber(streamPath, subscriber)
} else {
errSignal := NewErrorSignal(err.Error(), streamPath)
conf.Debug("sending error signal", "error", err.Error(), "stream_path", streamPath)
dataChannel.SendText(errSignal)
}
}
case SignalTypeUnpublish:
// Handle stream removal
if conn.Publisher != nil && conn.Publisher.StreamPath == signal.StreamPath {
conn.Publisher.Stop(task.ErrStopByUser)
conn.Publisher = nil
// Renegotiate SDP after unpublish
if answer, err := conn.GetAnswer(); err == nil {
answerSignal := NewAnswerSingal(answer.SDP)
conf.Debug("sending answer signal", "stream_path", signal.StreamPath)
dataChannel.SendText(answerSignal)
} else {
errSignal := NewErrorSignal(err.Error(), signal.StreamPath)
conf.Debug("sending error signal", "error", err.Error(), "stream_path", signal.StreamPath)
dataChannel.SendText(errSignal)
}
}
case SignalTypeAnswer:
// Handle received answer from browser
if err := conn.SetRemoteDescription(SessionDescription{
Type: SDPTypeAnswer,
SDP: signal.Answer,
}); err != nil {
errSignal := NewErrorSignal("Failed to set remote description: "+err.Error(), "")
conf.Debug("sending error signal", "error", err.Error())
dataChannel.SendText(errSignal)
}
}
// Then add new subscribers
for _, streamPath := range signal.StreamList {
// Skip if already subscribed
if conn.HasSubscriber(streamPath) {
continue
}
if subscriber, err := conf.Subscribe(conf.Context, streamPath); err == nil {
subscriber.RemoteAddr = r.RemoteAddr
conn.AddSubscriber(streamPath, subscriber)
} else {
dataChannel.SendText(NewErrorSignal(err.Error(), streamPath))
}
}
case SignalTypeUnpublish:
// Handle stream removal
if conn.Publisher != nil && conn.Publisher.StreamPath == signal.StreamPath {
conn.Publisher.Stop(task.ErrStopByUser)
conn.Publisher = nil
// Renegotiate SDP after unpublish
if answer, err := conn.GetAnswer(); err == nil {
dataChannel.SendText(NewAnswerSingal(answer.SDP))
} else {
dataChannel.SendText(NewErrorSignal(err.Error(), signal.StreamPath))
}
}
case SignalTypeAnswer:
// Handle received answer from browser
if err := conn.SetRemoteDescription(SessionDescription{
Type: SDPTypeAnswer,
SDP: signal.Answer,
}); err != nil {
dataChannel.SendText(NewErrorSignal("Failed to set remote description: "+err.Error(), ""))
}
}
})
})
conf.AddTask(conn)
@@ -235,3 +251,37 @@ func (conf *WebRTCPlugin) Batch(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
// 在Connection结构体中添加状态标记
type Connection struct {
// ...其他字段
initialOffer bool
}
func (conn *Connection) GetAnswer() (SessionDescription, error) {
if conn.initialOffer {
// 完整SDP处理
answer, err := conn.PeerConnection.CreateAnswer(nil)
conn.initialOffer = false
return answer, err
} else {
// 增量更新时生成部分SDP
return SessionDescription{
SDP: conn.generatePartialSDP(),
Type: SDPTypeAnswer,
}, nil
}
}
// 生成部分SDP的逻辑
func (conn *Connection) generatePartialSDP() string {
var sdp strings.Builder
// 这里简化实现,实际需要根据变化生成对应媒体部分
sdp.WriteString("v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\n")
for _, transceiver := range conn.PeerConnection.GetTransceivers() {
if transceiver.Direction() == RTPTransceiverDirectionRecvonly {
sdp.WriteString(fmt.Sprintf("m=video 9 UDP/TLS/RTP/SAVPF 96\r\na=recvonly\r\n"))
}
}
return sdp.String()
}