mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-16 12:30:47 +08:00
Start adapting new streamforward to Android
This commit is contained in:
262
pkg/recoder/libav/saferecoder/process/client/client.go
Normal file
262
pkg/recoder/libav/saferecoder/process/client/client.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/grpc/go/recoder_grpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Target string
|
||||
}
|
||||
|
||||
func New(target string) *Client {
|
||||
return &Client{Target: target}
|
||||
}
|
||||
|
||||
func (c *Client) grpcClient() (recoder_grpc.RecoderClient, *grpc.ClientConn, error) {
|
||||
conn, err := grpc.NewClient(
|
||||
c.Target,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to initialize a gRPC client: %w", err)
|
||||
}
|
||||
|
||||
client := recoder_grpc.NewRecoderClient(conn)
|
||||
return client, conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) SetLoggingLevel(
|
||||
ctx context.Context,
|
||||
logLevel logger.Level,
|
||||
) error {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = client.SetLoggingLevel(ctx, &recoder_grpc.SetLoggingLevelRequest{
|
||||
Level: logLevelGo2Protobuf(logLevel),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InputConfig = recoder.InputConfig
|
||||
type InputID uint64
|
||||
|
||||
func (c *Client) NewInputFromURL(
|
||||
ctx context.Context,
|
||||
url string,
|
||||
config InputConfig,
|
||||
) (InputID, error) {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
resp, err := client.NewInput(ctx, &recoder_grpc.NewInputRequest{
|
||||
Path: &recoder_grpc.ResourcePath{
|
||||
ResourcePath: &recoder_grpc.ResourcePath_Url{
|
||||
Url: url,
|
||||
},
|
||||
},
|
||||
Config: &recoder_grpc.InputConfig{},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
|
||||
return InputID(resp.GetId()), nil
|
||||
}
|
||||
|
||||
func (c *Client) CloseInput(
|
||||
ctx context.Context,
|
||||
inputID InputID,
|
||||
) error {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = client.CloseInput(ctx, &recoder_grpc.CloseInputRequest{
|
||||
InputID: uint64(inputID),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type OutputID uint64
|
||||
type OutputConfig = recoder.OutputConfig
|
||||
|
||||
func (c *Client) NewOutputFromURL(
|
||||
ctx context.Context,
|
||||
url string,
|
||||
config OutputConfig,
|
||||
) (OutputID, error) {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
resp, err := client.NewOutput(ctx, &recoder_grpc.NewOutputRequest{
|
||||
Path: &recoder_grpc.ResourcePath{
|
||||
ResourcePath: &recoder_grpc.ResourcePath_Url{
|
||||
Url: url,
|
||||
},
|
||||
},
|
||||
Config: &recoder_grpc.OutputConfig{},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
|
||||
return OutputID(resp.GetId()), nil
|
||||
}
|
||||
|
||||
func (c *Client) CloseOutput(
|
||||
ctx context.Context,
|
||||
outputID OutputID,
|
||||
) error {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = client.CloseOutput(ctx, &recoder_grpc.CloseOutputRequest{
|
||||
OutputID: uint64(outputID),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RecoderID uint64
|
||||
type RecoderConfig = recoder.Config
|
||||
|
||||
func (c *Client) NewRecoder(
|
||||
ctx context.Context,
|
||||
config RecoderConfig,
|
||||
) (RecoderID, error) {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
resp, err := client.NewRecoder(ctx, &recoder_grpc.NewRecoderRequest{
|
||||
Config: &recoder_grpc.RecoderConfig{},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
|
||||
return RecoderID(resp.GetId()), nil
|
||||
}
|
||||
|
||||
func (c *Client) StartRecoding(
|
||||
ctx context.Context,
|
||||
recoderID RecoderID,
|
||||
inputID InputID,
|
||||
outputID OutputID,
|
||||
) error {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, err = client.StartRecoding(ctx, &recoder_grpc.StartRecodingRequest{
|
||||
RecoderID: uint64(recoderID),
|
||||
InputID: uint64(inputID),
|
||||
OutputID: uint64(outputID),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RecoderStats = recoder.Stats
|
||||
|
||||
func (c *Client) GetRecoderStats(
|
||||
ctx context.Context,
|
||||
recoderID RecoderID,
|
||||
) (*RecoderStats, error) {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
resp, err := client.GetRecoderStats(ctx, &recoder_grpc.GetRecoderStatsRequest{
|
||||
RecoderID: uint64(recoderID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
|
||||
return &RecoderStats{
|
||||
BytesCountRead: resp.GetBytesCountRead(),
|
||||
BytesCountWrote: resp.GetBytesCountWrote(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) RecodingEndedChan(
|
||||
ctx context.Context,
|
||||
recoderID RecoderID,
|
||||
) (<-chan struct{}, error) {
|
||||
client, conn, err := c.grpcClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
waiter, err := client.RecodingEndedChan(ctx, &recoder_grpc.RecodingEndedChanRequest{
|
||||
RecoderID: uint64(recoderID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query error: %w", err)
|
||||
}
|
||||
|
||||
result := make(chan struct{})
|
||||
waiter.CloseSend()
|
||||
observability.Go(ctx, func() {
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
close(result)
|
||||
}()
|
||||
|
||||
_, err := waiter.Recv()
|
||||
if err == io.EOF {
|
||||
logger.Debugf(ctx, "the receiver is closed: %v", err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "unable to read data: %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
27
pkg/recoder/libav/saferecoder/process/client/logger.go
Normal file
27
pkg/recoder/libav/saferecoder/process/client/logger.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/grpc/go/recoder_grpc"
|
||||
)
|
||||
|
||||
func logLevelGo2Protobuf(logLevel logger.Level) recoder_grpc.LoggingLevel {
|
||||
switch logLevel {
|
||||
case logger.LevelFatal:
|
||||
return recoder_grpc.LoggingLevel_LoggingLevelFatal
|
||||
case logger.LevelPanic:
|
||||
return recoder_grpc.LoggingLevel_LoggingLevelPanic
|
||||
case logger.LevelError:
|
||||
return recoder_grpc.LoggingLevel_LoggingLevelError
|
||||
case logger.LevelWarning:
|
||||
return recoder_grpc.LoggingLevel_LoggingLevelWarn
|
||||
case logger.LevelInfo:
|
||||
return recoder_grpc.LoggingLevel_LoggingLevelInfo
|
||||
case logger.LevelDebug:
|
||||
return recoder_grpc.LoggingLevel_LoggingLevelDebug
|
||||
case logger.LevelTrace:
|
||||
return recoder_grpc.LoggingLevel_LoggingLevelTrace
|
||||
default:
|
||||
return recoder_grpc.LoggingLevel_LoggingLevelWarn
|
||||
}
|
||||
}
|
49
pkg/recoder/libav/saferecoder/process/init_libav.go
Normal file
49
pkg/recoder/libav/saferecoder/process/init_libav.go
Normal file
@@ -0,0 +1,49 @@
|
||||
//go:build with_libav
|
||||
// +build with_libav
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/facebookincubator/go-belt"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/process/server"
|
||||
)
|
||||
|
||||
const (
|
||||
EnvKeyIsRecoder = "IS_STREAMPANEL_RECODER"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if os.Getenv(EnvKeyIsRecoder) != "" {
|
||||
runRecoder()
|
||||
belt.Flush(context.TODO())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func runRecoder() {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to listen: %w", err))
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
d := ReturnedData{
|
||||
ListenAddr: listener.Addr().String(),
|
||||
}
|
||||
b, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "%s\n", b)
|
||||
|
||||
srv := server.NewServer()
|
||||
err = srv.Serve(context.TODO(), listener)
|
||||
panic(err)
|
||||
}
|
5
pkg/recoder/libav/saferecoder/process/returned_data.go
Normal file
5
pkg/recoder/libav/saferecoder/process/returned_data.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package process
|
||||
|
||||
type ReturnedData struct {
|
||||
ListenAddr string
|
||||
}
|
83
pkg/recoder/libav/saferecoder/process/run_libav.go
Normal file
83
pkg/recoder/libav/saferecoder/process/run_libav.go
Normal file
@@ -0,0 +1,83 @@
|
||||
//go:build with_libav
|
||||
// +build with_libav
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
child_process_manager "github.com/AgustinSRG/go-child-process-manager"
|
||||
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/process/client"
|
||||
)
|
||||
|
||||
type InputID = client.InputID
|
||||
type InputConfig = recoder.InputConfig
|
||||
|
||||
type OutputID = client.OutputID
|
||||
type OutputConfig = recoder.OutputConfig
|
||||
|
||||
type RecoderID = client.RecoderID
|
||||
type RecoderConfig = recoder.Config
|
||||
|
||||
type RecoderStats = recoder.Stats
|
||||
|
||||
type Recoder struct {
|
||||
*client.Client
|
||||
Cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func Run(
|
||||
ctx context.Context,
|
||||
) (*Recoder, error) {
|
||||
cmd := exec.Command(os.Args[0])
|
||||
cmd.Stderr = os.Stderr
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to initialize an stdout pipe: %w", err)
|
||||
}
|
||||
cmd.Env = append(os.Environ(), EnvKeyIsRecoder+"=1")
|
||||
err = child_process_manager.ConfigureCommand(cmd)
|
||||
errmon.ObserveErrorCtx(ctx, err)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to start a forked process of a libav-recoder: %w", err)
|
||||
}
|
||||
err = child_process_manager.AddChildProcess(cmd.Process)
|
||||
errmon.ObserveErrorCtx(ctx, err)
|
||||
|
||||
decoder := json.NewDecoder(stdout)
|
||||
var d ReturnedData
|
||||
err = decoder.Decode(&d)
|
||||
logger.Debugf(ctx, "got data: %#+v", d)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to un-JSON-ize the process output: %w", err)
|
||||
}
|
||||
|
||||
c := client.New(d.ListenAddr)
|
||||
level := observability.LogLevelFilter.GetLevel()
|
||||
if err := c.SetLoggingLevel(ctx, level); err != nil {
|
||||
return nil, fmt.Errorf("unable to set the logging level to %s: %w", level, err)
|
||||
}
|
||||
|
||||
return &Recoder{
|
||||
Client: c,
|
||||
Cmd: cmd,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Recoder) Kill() error {
|
||||
return r.Cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func (r *Recoder) Wait(ctx context.Context) error {
|
||||
_, err := r.Cmd.Process.Wait()
|
||||
return err
|
||||
}
|
84
pkg/recoder/libav/saferecoder/process/run_nolibav.go
Normal file
84
pkg/recoder/libav/saferecoder/process/run_nolibav.go
Normal file
@@ -0,0 +1,84 @@
|
||||
//go:build !with_libav
|
||||
// +build !with_libav
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder"
|
||||
)
|
||||
|
||||
type Recoder struct {
|
||||
*Client
|
||||
}
|
||||
|
||||
func (r *Recoder) Kill() error {
|
||||
return fmt.Errorf("not compiled with libav support")
|
||||
}
|
||||
|
||||
func Run(
|
||||
ctx context.Context,
|
||||
) (*Recoder, error) {
|
||||
return nil, fmt.Errorf("not compiled with libav support")
|
||||
}
|
||||
|
||||
type Client struct{}
|
||||
|
||||
type InputID uint64
|
||||
type InputConfig = recoder.InputConfig
|
||||
|
||||
type OutputID uint64
|
||||
type OutputConfig = recoder.OutputConfig
|
||||
|
||||
type RecoderID uint64
|
||||
type RecoderConfig = recoder.Config
|
||||
|
||||
func (c *Client) NewInputFromURL(
|
||||
ctx context.Context,
|
||||
url string,
|
||||
config InputConfig,
|
||||
) (InputID, error) {
|
||||
return 0, fmt.Errorf("not compiled with libav support")
|
||||
}
|
||||
|
||||
func (c *Client) NewOutputFromURL(
|
||||
ctx context.Context,
|
||||
url string,
|
||||
config OutputConfig,
|
||||
) (OutputID, error) {
|
||||
return 0, fmt.Errorf("not compiled with libav support")
|
||||
}
|
||||
|
||||
func (c *Client) StartRecoding(
|
||||
ctx context.Context,
|
||||
recoderID RecoderID,
|
||||
inputID InputID,
|
||||
outputID OutputID,
|
||||
) error {
|
||||
return fmt.Errorf("not compiled with libav support")
|
||||
}
|
||||
|
||||
func (c *Client) NewRecoder(
|
||||
ctx context.Context,
|
||||
config RecoderConfig,
|
||||
) (RecoderID, error) {
|
||||
return 0, fmt.Errorf("not compiled with libav support")
|
||||
}
|
||||
|
||||
type RecoderStats = recoder.Stats
|
||||
|
||||
func (c *Client) GetRecoderStats(
|
||||
ctx context.Context,
|
||||
recoderID RecoderID,
|
||||
) (*RecoderStats, error) {
|
||||
return nil, fmt.Errorf("not compiled with libav support")
|
||||
}
|
||||
|
||||
func (c *Client) RecodingEndedChan(
|
||||
ctx context.Context,
|
||||
recoderID RecoderID,
|
||||
) (<-chan struct{}, error) {
|
||||
return nil, fmt.Errorf("not compiled with libav support")
|
||||
}
|
29
pkg/recoder/libav/saferecoder/process/server/logger.go
Normal file
29
pkg/recoder/libav/saferecoder/process/server/logger.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/grpc/go/recoder_grpc"
|
||||
)
|
||||
|
||||
func logLevelProtobuf2Go(logLevel recoder_grpc.LoggingLevel) logger.Level {
|
||||
switch logLevel {
|
||||
case recoder_grpc.LoggingLevel_LoggingLevelNone:
|
||||
return logger.LevelFatal
|
||||
case recoder_grpc.LoggingLevel_LoggingLevelFatal:
|
||||
return logger.LevelFatal
|
||||
case recoder_grpc.LoggingLevel_LoggingLevelPanic:
|
||||
return logger.LevelPanic
|
||||
case recoder_grpc.LoggingLevel_LoggingLevelError:
|
||||
return logger.LevelError
|
||||
case recoder_grpc.LoggingLevel_LoggingLevelWarn:
|
||||
return logger.LevelWarning
|
||||
case recoder_grpc.LoggingLevel_LoggingLevelInfo:
|
||||
return logger.LevelInfo
|
||||
case recoder_grpc.LoggingLevel_LoggingLevelDebug:
|
||||
return logger.LevelDebug
|
||||
case recoder_grpc.LoggingLevel_LoggingLevelTrace:
|
||||
return logger.LevelTrace
|
||||
default:
|
||||
return logger.LevelUndefined
|
||||
}
|
||||
}
|
293
pkg/recoder/libav/saferecoder/process/server/server.go
Normal file
293
pkg/recoder/libav/saferecoder/process/server/server.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/facebookincubator/go-belt"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/recoder"
|
||||
"github.com/xaionaro-go/streamctl/pkg/recoder/libav/saferecoder/grpc/go/recoder_grpc"
|
||||
"github.com/xaionaro-go/streamctl/pkg/xcontext"
|
||||
"github.com/xaionaro-go/streamctl/pkg/xsync"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type RecoderID uint64
|
||||
type InputID uint64
|
||||
type OutputID uint64
|
||||
|
||||
type GRPCServer struct {
|
||||
recoder_grpc.UnimplementedRecoderServer
|
||||
|
||||
GRPCServer *grpc.Server
|
||||
IsStarted bool
|
||||
|
||||
BeltLocker xsync.Mutex
|
||||
Belt *belt.Belt
|
||||
|
||||
RecoderLocker xsync.Mutex
|
||||
Recoder map[RecoderID]*recoder.Recoder
|
||||
RecoderNextID atomic.Uint64
|
||||
|
||||
InputLocker xsync.Mutex
|
||||
Input map[InputID]*recoder.Input
|
||||
InputNextID atomic.Uint64
|
||||
|
||||
OutputLocker xsync.Mutex
|
||||
Output map[OutputID]*recoder.Output
|
||||
OutputNextID atomic.Uint64
|
||||
}
|
||||
|
||||
func NewServer() *GRPCServer {
|
||||
srv := &GRPCServer{
|
||||
GRPCServer: grpc.NewServer(),
|
||||
Recoder: make(map[RecoderID]*recoder.Recoder),
|
||||
Input: make(map[InputID]*recoder.Input),
|
||||
Output: make(map[OutputID]*recoder.Output),
|
||||
}
|
||||
recoder_grpc.RegisterRecoderServer(srv.GRPCServer, srv)
|
||||
return srv
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) Serve(
|
||||
ctx context.Context,
|
||||
listener net.Listener,
|
||||
) error {
|
||||
if srv.IsStarted {
|
||||
panic("this GRPC server was already started at least once")
|
||||
}
|
||||
srv.IsStarted = true
|
||||
srv.Belt = belt.CtxBelt(ctx)
|
||||
return srv.GRPCServer.Serve(listener)
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) belt() *belt.Belt {
|
||||
ctx := context.TODO()
|
||||
return xsync.DoR1(ctx, &srv.BeltLocker, func() *belt.Belt {
|
||||
return srv.Belt
|
||||
})
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) ctx(ctx context.Context) context.Context {
|
||||
return belt.CtxWithBelt(ctx, srv.belt())
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) SetLoggingLevel(
|
||||
ctx context.Context,
|
||||
req *recoder_grpc.SetLoggingLevelRequest,
|
||||
) (*recoder_grpc.SetLoggingLevelReply, error) {
|
||||
ctx = srv.ctx(ctx)
|
||||
srv.BeltLocker.Do(ctx, func() {
|
||||
logLevel := logLevelProtobuf2Go(req.GetLevel())
|
||||
l := logger.FromBelt(srv.Belt).WithLevel(logLevel)
|
||||
srv.Belt = srv.Belt.WithTool(logger.ToolID, l)
|
||||
})
|
||||
return &recoder_grpc.SetLoggingLevelReply{}, nil
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) NewInput(
|
||||
ctx context.Context,
|
||||
req *recoder_grpc.NewInputRequest,
|
||||
) (*recoder_grpc.NewInputReply, error) {
|
||||
ctx = srv.ctx(ctx)
|
||||
switch path := req.Path.GetResourcePath().(type) {
|
||||
case *recoder_grpc.ResourcePath_Url:
|
||||
return srv.newInputByURL(ctx, path, req.Config)
|
||||
default:
|
||||
return nil, fmt.Errorf("the support of path type '%T' is not implemented", path)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) newInputByURL(
|
||||
ctx context.Context,
|
||||
path *recoder_grpc.ResourcePath_Url,
|
||||
_ *recoder_grpc.InputConfig,
|
||||
) (*recoder_grpc.NewInputReply, error) {
|
||||
config := recoder.InputConfig{}
|
||||
input, err := recoder.NewInputFromURL(ctx, path.Url, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to initialize an input using URL '%s' and config %#+v", path.Url, config)
|
||||
}
|
||||
|
||||
inputID := xsync.DoR1(ctx, &srv.InputLocker, func() InputID {
|
||||
inputID := InputID(srv.InputNextID.Add(1))
|
||||
srv.Input[inputID] = input
|
||||
return inputID
|
||||
})
|
||||
return &recoder_grpc.NewInputReply{
|
||||
Id: uint64(inputID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) CloseInput(
|
||||
ctx context.Context,
|
||||
req *recoder_grpc.CloseInputRequest,
|
||||
) (*recoder_grpc.CloseInputReply, error) {
|
||||
inputID := InputID(req.GetInputID())
|
||||
err := xsync.DoR1(ctx, &srv.InputLocker, func() error {
|
||||
input := srv.Input[inputID]
|
||||
if input == nil {
|
||||
return fmt.Errorf("there is no open input with ID %d", inputID)
|
||||
}
|
||||
input.Close()
|
||||
delete(srv.Input, inputID)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &recoder_grpc.CloseInputReply{}, nil
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) NewOutput(
|
||||
ctx context.Context,
|
||||
req *recoder_grpc.NewOutputRequest,
|
||||
) (*recoder_grpc.NewOutputReply, error) {
|
||||
ctx = srv.ctx(ctx)
|
||||
switch path := req.Path.GetResourcePath().(type) {
|
||||
case *recoder_grpc.ResourcePath_Url:
|
||||
return srv.newOutputByURL(ctx, path, req.Config)
|
||||
default:
|
||||
return nil, fmt.Errorf("the support of path type '%T' is not implemented", path)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) newOutputByURL(
|
||||
ctx context.Context,
|
||||
path *recoder_grpc.ResourcePath_Url,
|
||||
_ *recoder_grpc.OutputConfig,
|
||||
) (*recoder_grpc.NewOutputReply, error) {
|
||||
config := recoder.OutputConfig{}
|
||||
output, err := recoder.NewOutputFromURL(ctx, path.Url, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to initialize an output using URL '%s' and config %#+v", path.Url, config)
|
||||
}
|
||||
|
||||
outputID := xsync.DoR1(ctx, &srv.OutputLocker, func() OutputID {
|
||||
outputID := OutputID(srv.OutputNextID.Add(1))
|
||||
srv.Output[outputID] = output
|
||||
return outputID
|
||||
})
|
||||
return &recoder_grpc.NewOutputReply{
|
||||
Id: uint64(outputID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) CloseOutput(
|
||||
ctx context.Context,
|
||||
req *recoder_grpc.CloseOutputRequest,
|
||||
) (*recoder_grpc.CloseOutputReply, error) {
|
||||
outputID := OutputID(req.GetOutputID())
|
||||
err := xsync.DoR1(ctx, &srv.InputLocker, func() error {
|
||||
output := srv.Output[outputID]
|
||||
if output == nil {
|
||||
return fmt.Errorf("there is no open output with ID %d", outputID)
|
||||
}
|
||||
output.Close()
|
||||
delete(srv.Output, outputID)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &recoder_grpc.CloseOutputReply{}, nil
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) NewRecoder(
|
||||
ctx context.Context,
|
||||
req *recoder_grpc.NewRecoderRequest,
|
||||
) (*recoder_grpc.NewRecoderReply, error) {
|
||||
ctx = srv.ctx(ctx)
|
||||
config := recoder.RecoderConfig{}
|
||||
recoderInstance := recoder.New(config)
|
||||
recoderID := xsync.DoR1(ctx, &srv.RecoderLocker, func() RecoderID {
|
||||
recoderID := RecoderID(srv.RecoderNextID.Add(1))
|
||||
srv.Recoder[recoderID] = recoderInstance
|
||||
return recoderID
|
||||
})
|
||||
return &recoder_grpc.NewRecoderReply{
|
||||
Id: uint64(recoderID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) GetRecoderStats(
|
||||
ctx context.Context,
|
||||
req *recoder_grpc.GetRecoderStatsRequest,
|
||||
) (*recoder_grpc.GetRecoderStatsReply, error) {
|
||||
recoderID := RecoderID(req.GetRecoderID())
|
||||
recoder := xsync.DoR1(ctx, &srv.RecoderLocker, func() *recoder.Recoder {
|
||||
return srv.Recoder[recoderID]
|
||||
})
|
||||
return &recoder_grpc.GetRecoderStatsReply{
|
||||
BytesCountRead: recoder.RecoderStats.BytesCountRead.Load(),
|
||||
BytesCountWrote: recoder.RecoderStats.BytesCountWrote.Load(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) StartRecoding(
|
||||
ctx context.Context,
|
||||
req *recoder_grpc.StartRecodingRequest,
|
||||
) (*recoder_grpc.StartRecodingReply, error) {
|
||||
ctx = srv.ctx(ctx)
|
||||
|
||||
recoderID := RecoderID(req.GetRecoderID())
|
||||
inputID := InputID(req.GetInputID())
|
||||
outputID := OutputID(req.GetOutputID())
|
||||
|
||||
srv.RecoderLocker.ManualLock(ctx)
|
||||
srv.InputLocker.ManualLock(ctx)
|
||||
srv.OutputLocker.ManualLock(ctx)
|
||||
defer srv.RecoderLocker.ManualUnlock(ctx)
|
||||
defer srv.InputLocker.ManualUnlock(ctx)
|
||||
defer srv.OutputLocker.ManualUnlock(ctx)
|
||||
|
||||
recoder := srv.Recoder[recoderID]
|
||||
if recoder == nil {
|
||||
return nil, fmt.Errorf("the recorder with ID '%v' does not exist", recoderID)
|
||||
}
|
||||
input := srv.Input[inputID]
|
||||
if input == nil {
|
||||
return nil, fmt.Errorf("the input with ID '%v' does not exist", inputID)
|
||||
}
|
||||
output := srv.Output[outputID]
|
||||
if output == nil {
|
||||
return nil, fmt.Errorf("the output with ID '%v' does not exist", outputID)
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(xcontext.DetachDone(ctx))
|
||||
err := recoder.StartRecoding(ctx, input, output)
|
||||
if err != nil {
|
||||
cancelFunc()
|
||||
return nil, fmt.Errorf("unable to start recoding")
|
||||
}
|
||||
|
||||
return &recoder_grpc.StartRecodingReply{}, nil
|
||||
}
|
||||
|
||||
func (srv *GRPCServer) RecodingEndedChan(
|
||||
req *recoder_grpc.RecodingEndedChanRequest,
|
||||
streamSrv recoder_grpc.Recoder_RecodingEndedChanServer,
|
||||
) (_ret error) {
|
||||
ctx := srv.ctx(streamSrv.Context())
|
||||
recoderID := RecoderID(req.GetRecoderID())
|
||||
|
||||
logger.Tracef(ctx, "RecodingEndedChan(%v)", recoderID)
|
||||
defer func() {
|
||||
logger.Tracef(ctx, "/RecodingEndedChan(%v): %v", recoderID, _ret)
|
||||
}()
|
||||
|
||||
recoder := xsync.DoR1(ctx, &srv.RecoderLocker, func() *recoder.Recoder {
|
||||
return srv.Recoder[recoderID]
|
||||
})
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-recoder.WaiterChan:
|
||||
}
|
||||
|
||||
return streamSrv.Send(&recoder_grpc.RecodingEndedChanReply{})
|
||||
}
|
Reference in New Issue
Block a user