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;
}
}
}