Files
go-mxl-player/internal/playback/audio_worker.go
T
2026-08-27 23:57:39 +03:00

185 lines
3.4 KiB
Go

package playback
import (
"context"
"errors"
"fmt"
)
var (
ErrAudioFactoryRequired = errors.New("audio reader factory is required")
ErrAudioSinkRequired = errors.New("audio sink is required")
ErrAudioRetryDeciderRequired = errors.New("audio decider is required")
ErrAudioFeedInactive = errors.New("audio feed is not active")
)
type AudioWorker struct {
factory AudioReaderFactory
sink AudioSink
retry RetryPolicy
shouldRetry retryDecider
observer StatusObserver
wait waitFunc
}
func NewAudioWorker(
factory AudioReaderFactory,
sink AudioSink,
retry RetryPolicy,
shouldRetry func(error) bool,
observer StatusObserver,
) (*AudioWorker, error) {
if factory == nil {
return nil, ErrAudioFactoryRequired
}
if sink == nil {
return nil, ErrAudioSinkRequired
}
if shouldRetry == nil {
return nil, ErrAudioRetryDeciderRequired
}
if err := retry.Validate(); err != nil {
return nil, fmt.Errorf("validate audio retry policy: %w", err)
}
return &AudioWorker{
factory: factory,
sink: sink,
retry: retry,
shouldRetry: shouldRetry,
observer: observer,
wait: waitForRetry,
}, nil
}
type stabilityAudioSink struct {
sink AudioSink
onStable func()
stable bool
}
func (s *stabilityAudioSink) ConsumeAudio(
ctx context.Context,
frame AudioFrame,
) error {
err := s.sink.ConsumeAudio(ctx, frame)
if err == nil && !s.stable {
s.stable = true
if s.onStable != nil {
s.onStable()
}
}
return err
}
func (w *AudioWorker) emit(status Status) {
if w.observer != nil {
w.observer(status)
}
}
func (w *AudioWorker) Run(
ctx context.Context,
config FeedConfig,
) error {
if err := config.Validate(); err != nil {
return fmt.Errorf("validate audio config: %w", err)
}
if !config.Active {
return ErrAudioFeedInactive
}
attemptNumber := 0
var latestRetry retryEvent
attempt := func(ctx context.Context) (bool, error) {
attemptNumber++
state := StateConnecting
if attemptNumber > 1 {
state = StateReconnecting
}
w.emit(Status{
Unit: UnitAudio,
State: state,
Attempt: attemptNumber,
})
attemptSink := &stabilityAudioSink{
sink: w.sink,
onStable: func() {
w.emit(Status{
Unit: UnitAudio,
State: StatePlaying,
Attempt: attemptNumber,
})
},
}
err := runAudioAttempt(ctx, w.factory, attemptSink, config)
return attemptSink.stable, err
}
decide := func(err error) bool {
var sinkErr *audioSinkError
if errors.As(err, &sinkErr) {
return false
}
return w.shouldRetry(err)
}
observeRetry := func(event retryEvent) {
latestRetry = event
if !event.WillRetry {
return
}
w.emit(Status{
Unit: UnitAudio,
State: StateReconnecting,
Attempt: attemptNumber + 1,
FailedAttempts: event.FailedAttempts,
RetryIn: event.RetryIn,
Err: event.Err,
})
}
err := runWithRetry(
ctx,
w.retry,
attempt,
decide,
w.wait,
observeRetry,
)
if ctx.Err() != nil {
w.emit(Status{
Unit: UnitAudio,
State: StateStopping,
})
w.emit(Status{
Unit: UnitAudio,
State: StateIdle,
})
return ctx.Err()
}
if err != nil {
w.emit(Status{
Unit: UnitAudio,
State: StateFailed,
Attempt: attemptNumber,
FailedAttempts: latestRetry.FailedAttempts,
Err: err,
})
return err
}
w.emit(Status{
Unit: UnitAudio,
State: StateIdle,
})
return nil
}