Refactoring #3
@@ -3,6 +3,7 @@ package mxladapter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"mxl-player/internal/source"
|
"mxl-player/internal/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func TestShouldRetrySourceError(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := ShouldRetry(tt.err); got != tt.want {
|
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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user