playback composition object

This commit is contained in:
Dmitry Sergeev
2026-09-01 00:43:57 +03:00
parent 03ebe5bbd0
commit 19e2e076e9
3 changed files with 170 additions and 1 deletions
+127
View File
@@ -0,0 +1,127 @@
package main
import (
"log"
mxladapter "mxl-player/internal/adapter/mxl"
"mxl-player/internal/output"
"mxl-player/internal/playback"
)
type playerPlayback struct {
Controller *playback.SessionController
Commands chan playback.SessionCommand
Video *playback.VideoBridge
Status *playback.StatusStore
Audio *output.SDLAudioSink
}
func newPlayerPlayback(
audioDevice uint32,
retry playback.RetryPolicy,
) (*playerPlayback, error) {
videoBridge := playback.NewVideoBridge()
statusStore := playback.NewStatusStore()
audioSink := output.NewSDLAudioSink(audioDevice)
observe := func(status playback.Status) {
statusStore.Observe(status)
if status.Err != nil {
log.Printf(
"%s: state=%v attempt=%d failed=%d: %v",
status.Unit,
status.State,
status.Attempt,
status.FailedAttempts,
status.Err,
)
return
}
log.Printf(
"%s: state=%v attempt=%d failed=%d",
status.Unit,
status.State,
status.Attempt,
status.FailedAttempts,
)
}
videoWorker, err := playback.NewVideoWorker(
mxladapter.VideoFactory{},
videoBridge,
retry,
mxladapter.ShouldRetry,
observe,
)
if err != nil {
_ = audioSink.Close()
return nil, err
}
videoSlot, err := playback.NewVideoSlot(videoWorker)
if err != nil {
_ = audioSink.Close()
return nil, err
}
audioWorker, err := playback.NewAudioWorker(
mxladapter.AudioFactory{},
audioSink,
retry,
mxladapter.ShouldRetry,
observe,
)
if err != nil {
_ = audioSink.Close()
return nil, err
}
audioSlot, err := playback.NewAudioSlot(audioWorker)
if err != nil {
_ = audioSink.Close()
return nil, err
}
syncWorker, err := playback.NewSyncWorker(
mxladapter.SyncFactory{},
videoBridge,
audioSink,
retry,
mxladapter.ShouldRetry,
observe,
)
if err != nil {
_ = audioSink.Close()
return nil, err
}
syncSlot, err := playback.NewSyncSlot(syncWorker)
if err != nil {
_ = audioSink.Close()
return nil, err
}
controller, err := playback.NewSessionController(
videoSlot,
audioSlot,
syncSlot,
func(video, audio playback.FeedConfig) bool {
return video.Domain == audio.Domain
},
)
if err != nil {
_ = audioSink.Close()
return nil, err
}
return &playerPlayback{
Controller: controller,
Commands: make(chan playback.SessionCommand, 32),
Video: videoBridge,
Status: statusStore,
Audio: audioSink,
}, nil
}
func (p *playerPlayback) Close() error {
return p.Audio.Close()
}
+42
View File
@@ -0,0 +1,42 @@
package main
import (
"testing"
"time"
"mxl-player/internal/playback"
)
func validRuntimeRetryPolicy() playback.RetryPolicy {
return playback.RetryPolicy{
MaxAttempts: 3,
InitialDelay: time.Millisecond,
MaxDelay: time.Second,
}
}
func TestNewPlayerPlaybackBuildsCompleteRuntime(t *testing.T) {
runtime, err := newPlayerPlayback(123, validRuntimeRetryPolicy())
if err != nil {
t.Fatalf("newPlayerPlayback() error = %v", err)
}
if runtime.Controller == nil || runtime.Video == nil || runtime.Status == nil || runtime.Audio == nil {
t.Fatalf("newPlayerPlayback() = %#v", runtime)
}
if runtime.Commands == nil || cap(runtime.Commands) != 32 {
t.Fatalf("command channel = %#v, capacity = %d", runtime.Commands, cap(runtime.Commands))
}
if err := runtime.Close(); err != nil {
t.Fatalf("Close() error = %v", err)
}
}
func TestNewPlayerPlaybackRejectsInvalidRetryPolicy(t *testing.T) {
runtime, err := newPlayerPlayback(123, playback.RetryPolicy{})
if runtime != nil {
t.Fatalf("newPlayerPlayback() runtime = %#v, want nil", runtime)
}
if err == nil {
t.Fatal("newPlayerPlayback() error is nil")
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ Size=200,200
Collapsed=0 Collapsed=0
[Window][Connection] [Window][Connection]
Pos=628,539 Pos=1322,937
Size=613,164 Size=613,164
Collapsed=0 Collapsed=0