refactor: replace get_stream_url with get_source_uri per ADR-0002

Providers now expose a source URI (for FFmpeg) instead of a
viewer-facing stream URL. Playout Service will own transcoding.

- Add SourceUri value object (NetworkUrl | FilePath)
- Remove StreamQuality, StreamingProtocol from domain
- Simplify ProviderCapabilities (drop streaming_protocol, transcode)
- Jellyfin: return static direct stream URL
- Local files: return absolute file path
- Rename use case get_stream_url -> get_source
- Stream endpoint returns JSON SourceUri instead of 307 redirect
This commit is contained in:
2026-07-12 07:34:13 +02:00
parent abcf69ce7e
commit 39f5f99bfd
21 changed files with 82 additions and 256 deletions

View File

@@ -11,7 +11,7 @@ use application::{
providers::ProviderDeps,
schedule::ScheduleDeps,
};
use domain::ports::{IMediaProvider, IProviderRegistry, ProviderCapabilities, StreamingProtocol};
use domain::ports::{IMediaProvider, IProviderRegistry, ProviderCapabilities};
use domain::{DomainError, ScheduleEngineService};
use infra_wiring::{Config, ConfigSource, DbPool};
@@ -267,9 +267,7 @@ impl IMediaProvider for NoopMediaProvider {
tags: false,
decade: false,
search: false,
streaming_protocol: StreamingProtocol::DirectFile,
rescan: false,
transcode: false,
}
}
@@ -291,11 +289,7 @@ impl IMediaProvider for NoopMediaProvider {
))
}
async fn get_stream_url(
&self,
_: &domain::MediaItemId,
_: &domain::ports::StreamQuality,
) -> domain::DomainResult<String> {
async fn get_source_uri(&self, _: &domain::MediaItemId) -> domain::DomainResult<domain::SourceUri> {
Err(DomainError::InfrastructureError(
"No media provider configured.".into(),
))
@@ -359,19 +353,18 @@ impl IProviderRegistry for SimpleProviderRegistry {
}
}
async fn get_stream_url(
async fn get_source_uri(
&self,
item_id: &domain::MediaItemId,
quality: &domain::ports::StreamQuality,
) -> domain::DomainResult<String> {
) -> domain::DomainResult<domain::SourceUri> {
let id_str = item_id.value();
if let Some(pid) = Self::extract_provider_id(id_str) {
if let Some(provider) = self.get(pid) {
return provider.get_stream_url(item_id, quality).await;
return provider.get_source_uri(item_id).await;
}
}
if let Some(provider) = self.primary() {
provider.get_stream_url(item_id, quality).await
provider.get_source_uri(item_id).await
} else {
Err(DomainError::InfrastructureError(
"No provider available".into(),

View File

@@ -8,7 +8,7 @@ use api_types::{
CurrentBroadcastResponse, ScheduleHistoryEntry, ScheduleResponse, SlotResponse,
};
use application::schedule::{
GenerateScheduleCommand, GetCurrentBroadcastQuery, GetEpgQuery, GetStreamUrlQuery,
GenerateScheduleCommand, GetCurrentBroadcastQuery, GetEpgQuery, GetSourceQuery,
};
use domain::value_objects::ChannelId;
@@ -70,13 +70,9 @@ pub async fn get_stream(
State(state): State<AppState>,
Path(id): Path<uuid::Uuid>,
) -> Result<axum::response::Response, AppError> {
let query = GetStreamUrlQuery { channel_id: id };
match application::schedule::get_stream_url::execute(&state.schedule_deps, query).await? {
Some(url) => Ok((
StatusCode::TEMPORARY_REDIRECT,
[("Location", url.as_str())],
)
.into_response()),
let query = GetSourceQuery { channel_id: id };
match application::schedule::get_source::execute(&state.schedule_deps, query).await? {
Some(uri) => Ok(Json(uri).into_response()),
None => Ok(StatusCode::NO_CONTENT.into_response()),
}
}