P6: remote video and audio play through the proxy
- The media proxy streams ranged requests from the origin (passing the range on, never caching), downloads and caches whole files otherwise, and serves cached files with range support. A PeerTube video is never fetched whole for one viewer, and clients still never contact the remote host. - PeerTube's fragmented MP4 files inside an HLS entry are read as variants, so HLS-only instances play too. - A remote Video or Audio post becomes one playable Mastodon attachment: the best MP4 up to 720p that carries both sound and picture, with its poster and duration; the card is kept only when nothing is playable. - nginx: /media/proxy/ with proxy_buffering off and a 600 s read timeout (applied on the box, with a backup). Checked live: a GoToSocial image through the proxy answers 206 with exactly the asked range when streamed, 200 when cached, and 206 with the right Content-Range from the cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
6f1ab0073c
commit
f9658a8e7a
10 files changed
+193
-22
No files matched your search
@@ -23,6 +23,7 @@ namespace PrivaPub.Infrastructure.Http
|
||||
Task<FetchedJson> GetJson(string url, string accept, Action<HttpRequestMessage> sign, CancellationToken token);
|
||||
bool FailedTemporarily(string url);
|
||||
Task<(Uri FinalUri, string Html)> GetPage(string url, CancellationToken token);
|
||||
Task<HttpResponseMessage> OpenMedia(string url, System.Net.Http.Headers.RangeHeaderValue range, CancellationToken token);
|
||||
Task<HttpResponseMessage> Send(HttpRequestMessage request, CancellationToken token);
|
||||
Task<(byte[] Bytes, string ContentType)> GetMedia(string url, long maxBytes, CancellationToken token);
|
||||
}
|
||||
@@ -229,6 +230,46 @@ namespace PrivaPub.Infrastructure.Http
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> OpenMedia(string url, System.Net.Http.Headers.RangeHeaderValue range, CancellationToken token)
|
||||
{
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var target) || !IsAllowed(target))
|
||||
return default;
|
||||
try
|
||||
{
|
||||
for (var hop = 0; hop <= MaxRedirects; hop++)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, target);
|
||||
request.Headers.Accept.ParseAdd("video/*, audio/*, image/*");
|
||||
request.Headers.Range = range;
|
||||
var response = await _httpClientFactory.CreateClient(ClientName).SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
|
||||
if (IsRedirect(response.StatusCode))
|
||||
{
|
||||
var location = response.Headers.Location;
|
||||
response.Dispose();
|
||||
var next = location == default ? default : location.IsAbsoluteUri ? location : new Uri(target, location);
|
||||
if (!IsAllowed(next))
|
||||
return default;
|
||||
target = next;
|
||||
continue;
|
||||
}
|
||||
var mediaType = response.Content.Headers.ContentType?.MediaType?.ToLowerInvariant();
|
||||
if (!response.IsSuccessStatusCode || mediaType == default || mediaType.Contains("svg")
|
||||
|| !(mediaType.StartsWith("video/") || mediaType.StartsWith("audio/") || mediaType.StartsWith("image/") || mediaType == "application/octet-stream"))
|
||||
{
|
||||
response.Dispose();
|
||||
return default;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or BlockedDestinationException or OperationCanceledException && !token.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation("Media stream {Url} refused: {Reason}", url, ex.Message);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
const int MaxPageBytes = 512 * 1024;
|
||||
|
||||
static async Task<byte[]> ReadPrefix(HttpContent content, int limit, CancellationToken token)
|
||||
|
||||
Reference in new issue
Block a user