using System.Net; using ErsatzTV.Core.Interfaces.Streaming; using Microsoft.Extensions.Logging; namespace ErsatzTV.Infrastructure.Streaming; /// /// Probes a media-server remote-stream URL over HTTP. /// /// /// Deliberately fail-open: only a definitive HTTP 404 reports the media as gone. A probe that /// times out, errors, or returns any other status must never turn a tune that would have worked /// into an error card, so every other outcome reports available. (ersatztv#473) /// public class HttpRemoteStreamProber( IHttpClientFactory httpClientFactory, ILogger logger) : IRemoteStreamProber { private static readonly TimeSpan ProbeTimeout = TimeSpan.FromSeconds(2); public async Task IsAvailable(string url, CancellationToken cancellationToken) { try { using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeoutCts.CancelAfter(ProbeTimeout); using var request = new HttpRequestMessage(HttpMethod.Get, url); // ask for a single byte; media servers vary in their HEAD support, and this exercises the // same redirect chain ffmpeg will follow request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(0, 0); using HttpClient client = httpClientFactory.CreateClient(); using HttpResponseMessage response = await client.SendAsync( request, HttpCompletionOption.ResponseHeadersRead, timeoutCts.Token); if (response.StatusCode is HttpStatusCode.NotFound) { logger.LogWarning("Media server reported 404 for remote stream {Url}", url); return false; } return true; } catch (Exception ex) { // fail open - a probe failure is not evidence that the media is gone logger.LogDebug(ex, "Unable to probe remote stream {Url}; assuming it is available", url); return true; } } }