add local MXL video adapter

This commit is contained in:
Dmitry Sergeev
2026-08-27 09:27:14 +03:00
parent 4f6ee895e7
commit 87b012db80
4 changed files with 168 additions and 1 deletions
+1
View File
@@ -3,6 +3,7 @@ package mxladapter
import (
"context"
"errors"
"mxl-player/internal/source"
)
+1 -1
View File
@@ -67,7 +67,7 @@ func TestShouldRetrySourceError(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ShouldRetry(tt.err); got != tt.want {
t.Errorf("shouldRetrySourceError() = %t, want %t", got, tt.want)
t.Errorf("ShouldRetry() = %t, want %t", got, tt.want)
}
})
}
+91
View File
@@ -0,0 +1,91 @@
package mxladapter
import (
"context"
"errors"
"fmt"
"time"
"mxl-player/internal/playback"
"mxl-player/internal/source"
)
const DefaultVideoReadTimeout = 200 * time.Millisecond
type VideoFactory struct {
ReadTimeout time.Duration
}
type videoReader struct {
source *source.Source
timeout time.Duration
}
var _ playback.VideoReaderFactory = VideoFactory{}
var _ playback.VideoReader = (*videoReader)(nil)
func (f VideoFactory) OpenVideo(
ctx context.Context,
config playback.FeedConfig,
) (playback.VideoReader, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
if err := config.Validate(); err != nil {
return nil, &source.SourceError{
Op: "validate video feed",
Kind: source.ErrorKindInvalidConfig,
Err: err,
}
}
if !config.IsConfigured() {
return nil, &source.SourceError{
Op: "validate video feed",
Kind: source.ErrorKindInvalidConfig,
Err: errors.New("video feed is not configured"),
}
}
src, err := source.Open(config.Domain, config.UUID)
if err != nil {
return nil, fmt.Errorf("open local MXL video: %w", err)
}
if err := ctx.Err(); err != nil {
_ = src.Close()
return nil, err
}
timeout := f.ReadTimeout
if timeout <= 0 {
timeout = DefaultVideoReadTimeout
}
return &videoReader{
source: src,
timeout: timeout,
}, nil
}
func (r *videoReader) ReadVideo(
ctx context.Context,
) (playback.VideoFrame, error) {
frame, err := r.source.NextCtx(ctx, r.timeout)
if err != nil {
return playback.VideoFrame{}, err
}
return playback.VideoFrame{
Index: frame.Index,
Width: frame.Width,
Height: frame.Height,
Stride: frame.Stride,
Size: frame.Size,
Invalid: frame.Invalid,
Payload: frame.Payload,
}, nil
}
func (r *videoReader) Close() error {
return r.source.Close()
}
+75
View File
@@ -0,0 +1,75 @@
package mxladapter
import (
"context"
"errors"
"testing"
"mxl-player/internal/playback"
"mxl-player/internal/source"
)
func TestVideoFactoryOpenVideoCanceled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
reader, err := (VideoFactory{}).OpenVideo(ctx, playback.FeedConfig{})
if reader != nil {
t.Fatal("OpenVideo() reader is not nil after cancellation")
}
if !errors.Is(err, context.Canceled) {
t.Fatalf("OpenVideo() error = %v, want context.Canceled", err)
}
}
func TestVideoFactoryOpenVideoRejectsInvalidConfig(t *testing.T) {
tests := []struct {
name string
config playback.FeedConfig
}{
{
name: "feed is not configured",
config: playback.FeedConfig{},
},
{
name: "active feed has no UUID",
config: playback.FeedConfig{
Domain: "/dev/shm/mxl",
Active: true,
},
},
{
name: "configured feed has no domain",
config: playback.FeedConfig{
UUID: "video-uuid",
Active: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reader, err := (VideoFactory{}).OpenVideo(
context.Background(),
tt.config,
)
if reader != nil {
t.Fatal("OpenVideo() reader is not nil for invalid config")
}
if err == nil {
t.Fatal("OpenVideo() error is nil for invalid config")
}
if got := source.KindOf(err); got != source.ErrorKindInvalidConfig {
t.Fatalf(
"source.KindOf(OpenVideo()) = %v, want %v",
got,
source.ErrorKindInvalidConfig,
)
}
if ShouldRetry(err) {
t.Fatal("ShouldRetry(OpenVideo()) = true for invalid config")
}
})
}
}