mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-27 01:15:52 +08:00
doc: add arch docs
This commit is contained in:
111
doc/arch/admin.md
Normal file
111
doc/arch/admin.md
Normal 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
157
doc/arch/alias.md
Normal 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
245
doc/arch/config.md
Normal 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
94
doc/arch/db.md
Normal 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
72
doc/arch/grpc.md
Normal 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
145
doc/arch/http.md
Normal 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
57
doc/arch/index.md
Normal 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
124
doc/arch/log.md
Normal 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
170
doc/arch/plugin.md
Normal 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
45
doc/arch/relay.md
Normal 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
101
doc/arch/task.md
Normal 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
111
doc_CN/arch/admin.md
Normal 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
0
doc_CN/arch/auth.md
Normal file
77
doc_CN/arch/catalog.md
Normal file
77
doc_CN/arch/catalog.md
Normal 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
291
doc_CN/arch/config.md
Normal 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
94
doc_CN/arch/db.md
Normal 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
72
doc_CN/arch/grpc.md
Normal 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
145
doc_CN/arch/http.md
Normal 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
57
doc_CN/arch/index.md
Normal 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
124
doc_CN/arch/log.md
Normal 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
170
doc_CN/arch/plugin.md
Normal 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. 最终处理
|
||||
- 执行注册的销毁回调函数
|
||||
- 记录销毁日志
|
||||
- 确保所有资源都被正确释放
|
||||
|
||||
销毁阶段的主要目标是确保插件完全清理所有资源,不留下任何残留状态,防止内存泄漏和资源泄露。
|
@@ -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()
|
||||
}
|
||||
|
Reference in New Issue
Block a user