mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-12-24 13:48:04 +08:00
306 lines
8.0 KiB
Markdown
306 lines
8.0 KiB
Markdown
# Plugin Development Guide
|
|
|
|
## 1. Prerequisites
|
|
|
|
### Development Tools
|
|
- Visual Studio Code
|
|
- Goland
|
|
- Cursor
|
|
|
|
### Install gRPC
|
|
```shell
|
|
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
|
|
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
|
|
```
|
|
|
|
### Install gRPC-Gateway
|
|
```shell
|
|
$ go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
|
|
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
|
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
|
```
|
|
|
|
### Project Setup
|
|
- Create a Go project, e.g., `MyPlugin`
|
|
- Create a `pkg` directory for exportable code
|
|
- Create a `pb` directory for gRPC proto files
|
|
- Create an `example` directory for testing the plugin
|
|
|
|
> You can also create a directory `xxx` directly in the monibuca project's plugin folder to store your plugin code
|
|
|
|
## 2. Create a Plugin
|
|
|
|
```go
|
|
package plugin_myplugin
|
|
import (
|
|
"m7s.live/v5"
|
|
)
|
|
|
|
var _ = m7s.InstallPlugin[MyPlugin]()
|
|
|
|
type MyPlugin struct {
|
|
m7s.Plugin
|
|
Foo string
|
|
}
|
|
```
|
|
- `MyPlugin` struct is the plugin definition, `Foo` is a plugin property that can be configured in the configuration file
|
|
- Must embed `m7s.Plugin` struct to provide basic plugin functionality
|
|
- `m7s.InstallPlugin[MyPlugin](...)` registers the plugin so it can be loaded by monibuca
|
|
|
|
### Provide Default Configuration
|
|
Example:
|
|
```go
|
|
const defaultConfig = m7s.DefaultYaml(`tcp:
|
|
listenaddr: :5554`)
|
|
|
|
var _ = m7s.InstallPlugin[MyPlugin](defaultConfig)
|
|
```
|
|
|
|
## 3. Implement Event Callbacks (Optional)
|
|
|
|
### Initialization Callback
|
|
```go
|
|
func (config *MyPlugin) OnInit() (err error) {
|
|
// Initialize things
|
|
return
|
|
}
|
|
```
|
|
Used for plugin initialization after configuration is loaded. Return an error if initialization fails, and the plugin will be disabled.
|
|
|
|
### TCP Request Callback
|
|
```go
|
|
func (config *MyPlugin) OnTCPConnect(conn *net.TCPConn) task.ITask {
|
|
|
|
}
|
|
```
|
|
Called when receiving TCP connection requests if TCP listening port is configured.
|
|
|
|
### UDP Request Callback
|
|
```go
|
|
func (config *MyPlugin) OnUDPConnect(conn *net.UDPConn) task.ITask {
|
|
|
|
}
|
|
```
|
|
Called when receiving UDP connection requests if UDP listening port is configured.
|
|
|
|
### QUIC Request Callback
|
|
```go
|
|
func (config *MyPlugin) OnQUICConnect(quic.Connection) task.ITask {
|
|
|
|
}
|
|
```
|
|
Called when receiving QUIC connection requests if QUIC listening port is configured.
|
|
|
|
## 4. HTTP Interface Callbacks
|
|
|
|
### Legacy v4 Callback Style
|
|
```go
|
|
func (config *MyPlugin) API_test1(rw http.ResponseWriter, r *http.Request) {
|
|
// do something
|
|
}
|
|
```
|
|
Accessible via `http://ip:port/myplugin/api/test1`
|
|
|
|
### Route Mapping Configuration
|
|
This method supports parameterized routing:
|
|
```go
|
|
func (config *MyPlugin) RegisterHandler() map[string]http.HandlerFunc {
|
|
return map[string]http.HandlerFunc{
|
|
"/test1/{streamPath...}": config.test1,
|
|
}
|
|
}
|
|
func (config *MyPlugin) test1(rw http.ResponseWriter, r *http.Request) {
|
|
streamPath := r.PathValue("streamPath")
|
|
// do something
|
|
}
|
|
```
|
|
|
|
## 5. Implement Push/Pull Clients
|
|
|
|
### Implement Push Client
|
|
Push client needs to implement IPusher interface and pass the creation method to InstallPlugin.
|
|
```go
|
|
type Pusher struct {
|
|
pullCtx m7s.PullJob
|
|
}
|
|
|
|
func (c *Pusher) GetPullJob() *m7s.PullJob {
|
|
return &c.pullCtx
|
|
}
|
|
|
|
func NewPusher(_ config.Push) m7s.IPusher {
|
|
return &Pusher{}
|
|
}
|
|
var _ = m7s.InstallPlugin[MyPlugin](NewPusher)
|
|
```
|
|
|
|
### Implement Pull Client
|
|
Pull client needs to implement IPuller interface and pass the creation method to InstallPlugin.
|
|
The following Puller inherits from m7s.HTTPFilePuller for basic file and HTTP pulling:
|
|
```go
|
|
type Puller struct {
|
|
m7s.HTTPFilePuller
|
|
}
|
|
|
|
func NewPuller(_ config.Pull) m7s.IPuller {
|
|
return &Puller{}
|
|
}
|
|
var _ = m7s.InstallPlugin[MyPlugin](NewPuller)
|
|
```
|
|
|
|
## 6. Implement gRPC Service
|
|
|
|
### Create `myplugin.proto` in `pb` Directory
|
|
```proto
|
|
syntax = "proto3";
|
|
import "google/api/annotations.proto";
|
|
import "google/protobuf/empty.proto";
|
|
package myplugin;
|
|
option go_package="m7s.live/v5/plugin/myplugin/pb";
|
|
|
|
service api {
|
|
rpc MyMethod (MyRequest) returns (MyResponse) {
|
|
option (google.api.http) = {
|
|
post: "/myplugin/api/bar"
|
|
body: "foo"
|
|
};
|
|
}
|
|
}
|
|
message MyRequest {
|
|
string foo = 1;
|
|
}
|
|
message MyResponse {
|
|
string bar = 1;
|
|
}
|
|
```
|
|
|
|
### Generate gRPC Code
|
|
Add to VSCode task.json:
|
|
```json
|
|
{
|
|
"type": "shell",
|
|
"label": "build pb myplugin",
|
|
"command": "protoc",
|
|
"args": [
|
|
"-I.",
|
|
"-I${workspaceRoot}/pb",
|
|
"--go_out=.",
|
|
"--go_opt=paths=source_relative",
|
|
"--go-grpc_out=.",
|
|
"--go-grpc_opt=paths=source_relative",
|
|
"--grpc-gateway_out=.",
|
|
"--grpc-gateway_opt=paths=source_relative",
|
|
"myplugin.proto"
|
|
],
|
|
"options": {
|
|
"cwd": "${workspaceRoot}/plugin/myplugin/pb"
|
|
}
|
|
}
|
|
```
|
|
Or run command in pb directory:
|
|
```shell
|
|
protoc -I. -I$ProjectFileDir$/pb --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative --grpc-gateway_out=. --grpc-gateway_opt=paths=source_relative myplugin.proto
|
|
```
|
|
Replace `$ProjectFileDir$` with the directory containing global pb files.
|
|
|
|
### Implement gRPC Service
|
|
Create api.go:
|
|
```go
|
|
package plugin_myplugin
|
|
import (
|
|
"context"
|
|
"m7s.live/m7s/v5"
|
|
"m7s.live/m7s/v5/plugin/myplugin/pb"
|
|
)
|
|
|
|
func (config *MyPlugin) MyMethod(ctx context.Context, req *pb.MyRequest) (*pb.MyResponse, error) {
|
|
return &pb.MyResponse{Bar: req.Foo}, nil
|
|
}
|
|
```
|
|
|
|
### Register gRPC Service
|
|
```go
|
|
package plugin_myplugin
|
|
import (
|
|
"m7s.live/v5"
|
|
"m7s.live/v5/plugin/myplugin/pb"
|
|
)
|
|
|
|
var _ = m7s.InstallPlugin[MyPlugin](&pb.Api_ServiceDesc, pb.RegisterApiHandler)
|
|
|
|
type MyPlugin struct {
|
|
pb.UnimplementedApiServer
|
|
m7s.Plugin
|
|
Foo string
|
|
}
|
|
```
|
|
|
|
### Additional RESTful Endpoints
|
|
Same as v4:
|
|
```go
|
|
func (config *MyPlugin) API_test1(rw http.ResponseWriter, r *http.Request) {
|
|
// do something
|
|
}
|
|
```
|
|
Accessible via GET request to `/myplugin/api/test1`
|
|
|
|
## 7. Publishing Streams
|
|
|
|
```go
|
|
publisher, err = p.Publish(streamPath, connectInfo)
|
|
```
|
|
The last two parameters are optional.
|
|
|
|
After obtaining the `publisher`, you can publish audio/video data using `publisher.WriteAudio` and `publisher.WriteVideo`.
|
|
|
|
### Define Audio/Video Data
|
|
If existing audio/video data formats don't meet your needs, you can define custom formats by implementing this interface:
|
|
```go
|
|
IAVFrame interface {
|
|
GetAllocator() *util.ScalableMemoryAllocator
|
|
SetAllocator(*util.ScalableMemoryAllocator)
|
|
Parse(*AVTrack) error
|
|
ConvertCtx(codec.ICodecCtx) (codec.ICodecCtx, IAVFrame, error)
|
|
Demux(codec.ICodecCtx) (any, error)
|
|
Mux(codec.ICodecCtx, *AVFrame)
|
|
GetTimestamp() time.Duration
|
|
GetCTS() time.Duration
|
|
GetSize() int
|
|
Recycle()
|
|
String() string
|
|
Dump(byte, io.Writer)
|
|
}
|
|
```
|
|
> Define separate types for audio and video
|
|
|
|
- GetAllocator/SetAllocator: Automatically implemented when embedding RecyclableMemory
|
|
- Parse: Identifies key frames, sequence frames, and other important information
|
|
- ConvertCtx: Called when protocol conversion is needed
|
|
- Demux: Called when audio/video data needs to be demuxed
|
|
- Mux: Called when audio/video data needs to be muxed
|
|
- Recycle: Automatically implemented when embedding RecyclableMemory
|
|
- String: Prints audio/video data information
|
|
- GetSize: Gets the size of audio/video data
|
|
- GetTimestamp: Gets the timestamp in nanoseconds
|
|
- GetCTS: Gets the Composition Time Stamp in nanoseconds (PTS = DTS+CTS)
|
|
- Dump: Prints binary audio/video data
|
|
|
|
## 8. Subscribing to Streams
|
|
```go
|
|
var suber *m7s.Subscriber
|
|
suber, err = p.Subscribe(ctx,streamPath)
|
|
go m7s.PlayBlock(suber, handleAudio, handleVideo)
|
|
```
|
|
Note that handleAudio and handleVideo are callback functions you need to implement. They take an audio/video format type as input and return an error. If the error is not nil, the subscription is terminated.
|
|
|
|
## 9. Prometheus Integration
|
|
Just implement the Collector interface, and the system will automatically collect metrics from all plugins:
|
|
```go
|
|
func (p *MyPlugin) Describe(ch chan<- *prometheus.Desc) {
|
|
|
|
}
|
|
|
|
func (p *MyPlugin) Collect(ch chan<- prometheus.Metric) {
|
|
|
|
}
|
|
``` |