using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.Enigma2; using MediaBrowser.Controller.MediaEncoding; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; public static class PatientStartupE2E { private static int connections, active, maximum; public static async Task Run(string encoder, string folder) { folder=Path.GetFullPath(folder);if(Directory.Exists(folder))throw new Exception("Choose a new output directory");Directory.CreateDirectory(folder); var file=Path.Combine(folder,"fixture.ts"); var info=new ProcessStartInfo(encoder){UseShellExecute=false,RedirectStandardError=true}; foreach(var a in new[]{"-v","error","-nostdin","-filter_threads","1","-f","lavfi","-i","testsrc2=size=320x180:rate=25","-f","lavfi","-i","sine=sample_rate=48000","-t","4","-c:v","libx264","-threads","1","-preset","ultrafast","-g","25","-c:a","aac","-ac","2","-f","mpegts",file})info.ArgumentList.Add(a); using(var make=Process.Start(info)){var error=make.StandardError.ReadToEndAsync();await make.WaitForExitAsync();if(make.ExitCode!=0)throw new Exception(await error);} var bytes=await File.ReadAllBytesAsync(file); using var listener=new TcpListener(IPAddress.Loopback,0);listener.Start();var port=((IPEndPoint)listener.LocalEndpoint).Port;listener.Stop(); var builder=WebApplication.CreateBuilder();builder.Logging.ClearProviders();builder.WebHost.UseUrls($"http://127.0.0.1:{port}"); using var relay=new LiveStreamRelay(new System.Net.Http.HttpClient(),port,TimeSpan.FromMilliseconds(200),TimeSpan.FromSeconds(StartupBudget.Seconds),TimeSpan.FromSeconds(StartupBudget.StallSeconds)); builder.Services.AddSingleton(relay);builder.Services.AddControllers().AddApplicationPart(typeof(LiveRelayController).Assembly); await using var app=builder.Build();app.MapControllers(); app.MapGet("/late.ts",async context=> { Interlocked.Increment(ref connections);maximum=Math.Max(maximum,Interlocked.Increment(ref active)); context.Response.ContentType="video/mp2t";await context.Response.StartAsync(context.RequestAborted); try { await Task.Delay(TimeSpan.FromSeconds(35),context.RequestAborted); while(!context.RequestAborted.IsCancellationRequested) for(var at=0;at();((PatientEncoderProxy)(object)proxy).ProbePath=Path.Combine(Path.GetDirectoryName(encoder),"ffprobe"); var probe=new LiveStreamProbe(proxy);var url=relay.Register($"http://127.0.0.1:{port}/late.ts");var watch=Stopwatch.StartNew(); var result=await Task.WhenAll(Enumerable.Range(0,4).Select(_=>probe.Get(url,CancellationToken.None,1))).WaitAsync(TimeSpan.FromSeconds(95)); if(watch.Elapsed.TotalSeconds<35||watch.Elapsed.TotalSeconds>=90||result.Any(x=>x[0].Width!=320||x[0].Height!=180))throw new Exception("Delayed source failed the configured budget"); await Task.Delay(800); if(maximum!=1||connections!=1||active!=0)throw new Exception("Receiver sharing or cleanup failed"); var report=new{Passed=true,SyntheticOnly=true,ReceiverConnectionsOpened=0,ConfiguredStartupSeconds=StartupBudget.Seconds,DelayedFirstDataSeconds=35, ElapsedSeconds=watch.Elapsed.TotalSeconds,ConcurrentRequesters=4,PhysicalFixtureConnections=connections,MaximumConcurrentFixtureConnections=maximum,RemainingConnections=active, ActualFfprobeUsed=true,PhysicalAndroidTvTested=false}; await File.WriteAllTextAsync(Path.Combine(folder,"result.json"),JsonSerializer.Serialize(report,new JsonSerializerOptions{WriteIndented=true}));Console.WriteLine(JsonSerializer.Serialize(report)); } finally{relay.Dispose();await app.StopAsync();} } } public class PatientEncoderProxy : DispatchProxy { public string ProbePath; protected override object Invoke(MethodInfo method,object[] args)=>method.Name=="get_ProbePath"?ProbePath:null; }