using System.Net; using System.Net.Http.Headers; using System.Runtime.InteropServices; using LibVLCSharp.Shared; using Avalonia.Controls; using Avalonia.Headless; using PostIt.Services; using Yavsc.Api.Client; namespace PostIt.Tests; public sealed class PodcastReplayTests { private static readonly Guid Id = Guid.NewGuid(); private readonly CancellationToken _ct = TestContext.Current.CancellationToken; public static bool HasNativeFixture => File.Exists(Environment.GetEnvironmentVariable("POSTIT_TEST_REPLAY")); [Fact(Skip = "Set POSTIT_TEST_REPLAY to a local MP4 (8+ seconds) and install LibVLC.", SkipUnless = nameof(HasNativeFixture))] public async Task LibVLC_decodes_authenticated_MP4_ranges_and_seeks_in_both_directions() { using var transport = new RangeTransport { Bytes = await File.ReadAllBytesAsync(Environment.GetEnvironmentVariable("POSTIT_TEST_REPLAY")!, _ct) }; var api = new PodcastApiClient(transport, () => "https://api.example/"); Core.Initialize(); using var vlc = new LibVLC("--vout=dummy", "--aout=dummy", "--no-xlib"); using var input = new PodcastReplayMediaInput(api, Id, transport.Bytes.Length); using var media = new Media(vlc, input); using var player = new MediaPlayer(vlc); string? failure = null; input.Failed += message => failure = message; var ended = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); player.EndReached += (_, _) => ended.TrySetResult(); player.EncounteredError += (_, _) => ended.TrySetException(new IOException("LibVLC MP4 decoding failed.")); Assert.True(player.Play(media)); try { using var timeout = CancellationTokenSource.CreateLinkedTokenSource(_ct); timeout.CancelAfter(TimeSpan.FromSeconds(30)); while (player.Length <= 0 || !player.IsSeekable || player.Time <= 0) await Task.Delay(20, timeout.Token); Assert.InRange(player.Length, 8000, 30000); var forward = player.Length / 2; player.Time = forward; while (player.Time < forward - 300) await Task.Delay(20, timeout.Token); Assert.InRange(player.Time, forward - 300, forward + 1000); player.Time = 1000; while (player.Time > 2500) await Task.Delay(20, timeout.Token); Assert.InRange(player.Time, 500, 2500); await ended.Task.WaitAsync(timeout.Token); Assert.Null(failure); Assert.Contains(media.Tracks, t => t.TrackType == TrackType.Video && t.Codec == 0x34363268); Assert.Contains(media.Tracks, t => t.TrackType == TrackType.Audio && t.Codec == 0x6134706d); } finally { input.Cancel(); player.Stop(); } } [Fact] public Task Desktop_overlay_has_real_position_pause_and_duration_controls() => HeadlessUnitTestSession.GetOrStartForAssembly(typeof(PodcastReplayTests).Assembly).Dispatch(() => { var overlay = new PostIt.Controls.ReplayControls(); var window = new Window { Content = overlay, Width = 640, Height = 360 }; try { window.Show(); Assert.NotNull(overlay.FindControl("PositionSlider")); Assert.NotNull(overlay.FindControl