using System;
using System.Globalization;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Jellypod.Api;
///
/// Controller for proxying podcast audio streams.
///
[ApiController]
[Route("Jellypod/Stream")]
public class StreamProxyController : ControllerBase
{
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
///
/// Initializes a new instance of the class.
///
/// Logger instance.
/// HTTP client factory.
public StreamProxyController(
ILogger logger,
IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
///
/// Proxies an audio stream from a remote URL.
///
/// Base64-encoded URL to stream.
/// Cancellation token.
/// The audio stream.
[HttpGet("{url}")]
[AllowAnonymous]
public async Task GetStream([FromRoute] string url, CancellationToken cancellationToken)
{
try
{
// Decode the URL from base64
var decodedUrl = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(url));
_logger.LogDebug("Proxying audio stream from: {Url}", decodedUrl);
var client = _httpClientFactory.CreateClient("Jellypod");
// Make a streaming request
var request = new HttpRequestMessage(HttpMethod.Get, decodedUrl);
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Failed to fetch audio stream: {StatusCode}", response.StatusCode);
return StatusCode((int)response.StatusCode);
}
// Get content type
var contentType = response.Content.Headers.ContentType?.MediaType ?? "audio/mpeg";
var contentLength = response.Content.Headers.ContentLength;
// Stream the response
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
if (contentLength.HasValue)
{
Response.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture);
}
Response.Headers["Accept-Ranges"] = "bytes";
return File(stream, contentType, enableRangeProcessing: true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error proxying audio stream");
return StatusCode(500, "Failed to proxy audio stream");
}
}
}