mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-10-06 21:55:39 -04:00
fix(playback): preserve Apple stream quality on startup
This commit is contained in:
4 files changed
+37
-13
No files matched your search
@@ -148,7 +148,7 @@ public sealed class AppleDownloadCapabilityAdapterTests : IDisposable
|
||||
"apple/track 1");
|
||||
|
||||
var lease = (await adapter.GetStreamLeaseAsync(
|
||||
Context(tenant, user), new(track))).RequireValue();
|
||||
Context(tenant, user), new(track, ProviderAudioQuality.HighResolution))).RequireValue();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, lease.ProtectedSourceUri);
|
||||
using var response = await lease.ProtectedResponseFactory!(request, CancellationToken.None);
|
||||
|
||||
@@ -162,7 +162,7 @@ public sealed class AppleDownloadCapabilityAdapterTests : IDisposable
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(audio, await response.Content.ReadAsByteArrayAsync());
|
||||
Assert.Contains(gateway.Requests, uri =>
|
||||
uri.PathAndQuery == "/api/stream/apple%2Ftrack%201?quality=aac-320");
|
||||
uri.PathAndQuery == "/api/stream/apple%2Ftrack%201?quality=alac-24-96");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -47,12 +47,7 @@ public sealed class AppleDownloadStreamingCapabilityAdapter : IProviderStreaming
|
||||
return ProviderOutcome<ProviderStreamLease>.Failure(new(ErrorFor(snapshot.State)));
|
||||
if (!OutboundRequestGuard.TryCreateConfiguredServiceUri(settings.BaseUrl, out var baseUri, out _))
|
||||
return ProviderOutcome<ProviderStreamLease>.Failure(new(ProviderErrorKind.AccountNeedsConfiguration));
|
||||
// Cold lossless preparation can outlast music-client startup timeouts.
|
||||
// Keep configured lossless quality for downloads; default playback starts at AAC 320.
|
||||
var playbackQuality = request.RequestedQuality == ProviderAudioQuality.Any
|
||||
? ProviderAudioQuality.Lossy
|
||||
: request.RequestedQuality;
|
||||
var quality = AppleDownloadCapabilityAdapter.Quality(playbackQuality, settings.Quality);
|
||||
var quality = AppleDownloadCapabilityAdapter.Quality(request.RequestedQuality, settings.Quality);
|
||||
var source = new Uri(baseUri!,
|
||||
$"api/stream/{Uri.EscapeDataString(request.TrackId.Value)}?quality={Uri.EscapeDataString(quality)}");
|
||||
return ProviderOutcome<ProviderStreamLease>.Success(new(
|
||||
|
||||
@@ -5,7 +5,7 @@ import shutil
|
||||
import uuid
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, AsyncIterator
|
||||
|
||||
import httpx
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
@@ -274,12 +274,24 @@ def create_app(
|
||||
|
||||
@application.get("/api/stream/{song_id}")
|
||||
async def stream_song(song_id: str, quality: str = "alac-16-44") -> StreamingResponse:
|
||||
root, source = await prepare_song(song_id, quality, "aac-web")
|
||||
async def content() -> AsyncIterator[bytes]:
|
||||
root: Path | None = None
|
||||
try:
|
||||
root, source = await prepare_song(song_id, quality, "aac-web")
|
||||
async for chunk in process_runner.stream_flac(source):
|
||||
yield chunk
|
||||
finally:
|
||||
if root is not None:
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
|
||||
return StreamingResponse(
|
||||
process_runner.stream_flac(source),
|
||||
content(),
|
||||
media_type="audio/flac",
|
||||
headers={"Content-Disposition": f'inline; filename="{song_id}.flac"'},
|
||||
background=BackgroundTask(shutil.rmtree, root, ignore_errors=True),
|
||||
headers={
|
||||
"Content-Disposition": f'inline; filename="{song_id}.flac"',
|
||||
"Cache-Control": "no-store",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
@application.head("/api/stream/{song_id}")
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import Any
|
||||
import httpx
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from apple_gateway.app import API_VERSION, create_app
|
||||
from apple_gateway.catalog import CatalogClient
|
||||
@@ -247,6 +248,22 @@ def test_song_stream_head_reports_only_known_facts_without_preparing_media(clien
|
||||
assert client[0].head("/api/stream/102", params={"quality": "made-up"}).status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_song_stream_opens_before_preparing_configured_quality(settings: Settings):
|
||||
runner = FakeRunner()
|
||||
app = create_app(settings, FakeWrapper(), FakeCatalog(), runner)
|
||||
route = next(route for route in app.routes if getattr(route, "path", None) == "/api/stream/{song_id}")
|
||||
|
||||
response = await route.endpoint("102", "alac-16-44")
|
||||
|
||||
assert isinstance(response, StreamingResponse)
|
||||
assert response.headers["cache-control"] == "no-store"
|
||||
assert response.headers["x-accel-buffering"] == "no"
|
||||
assert runner.calls == []
|
||||
assert b"".join([chunk async for chunk in response.body_iterator]) == b"fLaCfixture"
|
||||
assert runner.calls[-1][1] == "alac"
|
||||
|
||||
|
||||
def test_song_stream_falls_back_to_web_aac_when_lossless_is_unavailable(settings):
|
||||
class FallbackRunner(FakeRunner):
|
||||
async def download(self, url: str, quality: str, output: Path, temporary: Path) -> list[Path]:
|
||||
|
||||
Reference in new issue
Block a user