package mxladapter import ( "context" "errors" "testing" "time" "mxl-player/internal/playback" "mxl-player/internal/source" mxl "github.com/qvest-digital/go-mxl/mxl" ) type fakeLocalSyncSource struct { video source.Frame audio source.AudioFrame readErr error rate mxl.Rational timeout time.Duration closed bool closeError error } func (s *fakeLocalSyncSource) NextSync( _ context.Context, timeout time.Duration, ) (source.Frame, source.AudioFrame, error) { s.timeout = timeout return s.video, s.audio, s.readErr } func (s *fakeLocalSyncSource) AudioRate() mxl.Rational { return s.rate } func (s *fakeLocalSyncSource) Close() error { s.closed = true return s.closeError } func syncFeedConfigs() (playback.FeedConfig, playback.FeedConfig) { return playback.FeedConfig{Domain: "/mxl", UUID: "video", Active: true}, playback.FeedConfig{Domain: "/mxl", UUID: "audio", Active: true} } func TestSyncFactoryRejectsDifferentDomains(t *testing.T) { video, audio := syncFeedConfigs() audio.Domain = "/other" reader, err := (SyncFactory{}).OpenSync(context.Background(), video, audio) if reader != nil { t.Fatal("OpenSync() reader is not nil") } if !errors.Is(err, ErrNativeSyncDifferentDomains) { t.Fatalf("OpenSync() error = %v, want %v", err, ErrNativeSyncDifferentDomains) } if source.KindOf(err) != source.ErrorKindInvalidConfig { t.Fatalf("error kind = %v, want invalid config", source.KindOf(err)) } if ShouldRetry(err) { t.Fatal("ShouldRetry() = true for different domains") } } func TestSyncFactoryUsesDefaultsAndForwardsFeeds(t *testing.T) { fake := &fakeLocalSyncSource{rate: mxl.Rational{Num: 48_000, Den: 1}} var domain, videoUUID, audioUUID string factory := SyncFactory{open: func(d, v, a string) (localSyncSource, error) { domain, videoUUID, audioUUID = d, v, a return fake, nil }} video, audio := syncFeedConfigs() reader, err := factory.OpenSync(context.Background(), video, audio) if err != nil { t.Fatalf("OpenSync() error = %v", err) } if domain != video.Domain || videoUUID != video.UUID || audioUUID != audio.UUID { t.Fatalf("open args = %q %q %q", domain, videoUUID, audioUUID) } got := reader.(*syncReader) if got.readTimeout != DefaultSyncReadTimeout { t.Fatalf("reader timeout=%s, want %s", got.readTimeout, DefaultSyncReadTimeout) } } func TestSyncReaderConvertsPairWithoutCopying(t *testing.T) { videoPayload := []byte{1, 2, 3} audioSamples := [][]byte{{4, 5, 6, 7}} fake := &fakeLocalSyncSource{ video: source.Frame{Index: 10, Width: 20, Height: 30, Payload: videoPayload}, audio: source.AudioFrame{Index: 40, SampleCount: 1, Channels: 1, Samples: audioSamples}, rate: mxl.Rational{Num: 48_000, Den: 1}, } reader := &syncReader{ source: fake, readTimeout: 7 * time.Millisecond, rateNumerator: 48_000, rateDenominator: 1, } frame, err := reader.ReadSync(context.Background()) if err != nil { t.Fatal(err) } if fake.timeout != 7*time.Millisecond { t.Fatalf("NextSync() timeout=%s", fake.timeout) } if frame.Video.Index != 10 || frame.Audio.Index != 40 || frame.Audio.SampleRateNumerator != 48_000 { t.Fatalf("frame = %+v", frame) } if &frame.Video.Payload[0] != &videoPayload[0] || &frame.Audio.Samples[0][0] != &audioSamples[0][0] { t.Fatal("sync payload was copied") } } func TestSyncFactoryReturnsPreCanceledContextWithoutOpening(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() opened := false factory := SyncFactory{open: func(string, string, string) (localSyncSource, error) { opened = true return nil, nil }} video, audio := syncFeedConfigs() reader, err := factory.OpenSync(ctx, video, audio) if reader != nil || !errors.Is(err, context.Canceled) || opened { t.Fatalf("reader=%v error=%v opened=%t", reader, err, opened) } }