From 39f5f99bfd34490b071a3427902d18bdf7f03a85 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 12 Jul 2026 07:34:13 +0200 Subject: [PATCH] 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 --- crates/adapters/jellyfin/src/models.rs | 14 ---- crates/adapters/jellyfin/src/provider.rs | 81 +++---------------- crates/adapters/local-files/src/lib.rs | 2 +- crates/adapters/local-files/src/provider.rs | 60 +++----------- crates/api-types/src/config.rs | 6 -- crates/application/src/config/get_config.rs | 2 - .../application/src/library/tests/helpers.rs | 14 +--- .../{get_stream_url.rs => get_source.rs} | 13 ++- crates/application/src/schedule/mod.rs | 4 +- crates/application/src/schedule/queries.rs | 2 +- .../application/src/schedule/tests/helpers.rs | 14 +--- crates/domain/src/ports/media.rs | 29 +------ crates/domain/src/ports/mod.rs | 1 - crates/domain/src/testing/noops.rs | 12 +-- crates/domain/src/value_objects/mod.rs | 2 + crates/domain/src/value_objects/streaming.rs | 8 ++ crates/mcp/src/main.rs | 16 ++-- crates/presentation/src/factory.rs | 19 ++--- crates/presentation/src/handlers/schedule.rs | 12 +-- crates/worker/src/jobs/library_sync.rs | 9 +-- crates/worker/src/main.rs | 18 ++--- 21 files changed, 82 insertions(+), 256 deletions(-) rename crates/application/src/schedule/{get_stream_url.rs => get_source.rs} (65%) create mode 100644 crates/domain/src/value_objects/streaming.rs diff --git a/crates/adapters/jellyfin/src/models.rs b/crates/adapters/jellyfin/src/models.rs index d5cc016..1b1c42c 100644 --- a/crates/adapters/jellyfin/src/models.rs +++ b/crates/adapters/jellyfin/src/models.rs @@ -37,20 +37,6 @@ pub(crate) struct JellyfinItem { pub recursive_item_count: Option, } -#[derive(Debug, Deserialize)] -pub(crate) struct JellyfinPlaybackInfoResponse { - #[serde(rename = "MediaSources")] - pub media_sources: Vec, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct JellyfinMediaSource { - #[serde(rename = "SupportsDirectStream")] - pub supports_direct_stream: bool, - #[serde(rename = "DirectStreamUrl")] - pub direct_stream_url: Option, -} - pub(crate) fn jellyfin_item_type(ct: &ContentType) -> &'static str { match ct { ContentType::Movie => "Movie", diff --git a/crates/adapters/jellyfin/src/provider.rs b/crates/adapters/jellyfin/src/provider.rs index 68a4ec5..a40f9f2 100644 --- a/crates/adapters/jellyfin/src/provider.rs +++ b/crates/adapters/jellyfin/src/provider.rs @@ -1,18 +1,11 @@ use async_trait::async_trait; -use domain::ports::{ - Collection, IMediaProvider, ProviderCapabilities, SeriesSummary, StreamQuality, - StreamingProtocol, -}; -use domain::{ContentType, DomainError, DomainResult, MediaFilter, MediaItem, MediaItemId}; +use domain::ports::{Collection, IMediaProvider, ProviderCapabilities, SeriesSummary}; +use domain::{ContentType, DomainError, DomainResult, MediaFilter, MediaItem, MediaItemId, SourceUri}; use crate::config::JellyfinConfig; use crate::mapping::{map_jellyfin_item, TICKS_PER_SEC}; -use crate::models::{ - jellyfin_item_type, JellyfinItemsResponse, JellyfinPlaybackInfoResponse, -}; - -const FALLBACK_HLS_BITRATE: u32 = 8_000_000; +use crate::models::{jellyfin_item_type, JellyfinItemsResponse}; pub struct JellyfinMediaProvider { client: reqwest::Client, @@ -129,16 +122,6 @@ impl JellyfinMediaProvider { Ok(items) } - fn hls_url(&self, item_id: &MediaItemId, bitrate: u32) -> String { - format!( - "{}/Videos/{}/master.m3u8?videoCodec=h264&audioCodec=aac&VideoBitRate={}&mediaSourceId={}&SubtitleMethod=Hls&subtitleCodec=vtt&api_key={}", - self.config.base_url, - item_id.as_ref(), - bitrate, - item_id.as_ref(), - self.config.api_key, - ) - } } #[async_trait] @@ -151,9 +134,7 @@ impl IMediaProvider for JellyfinMediaProvider { tags: true, decade: true, search: true, - streaming_protocol: StreamingProtocol::Hls, rescan: false, - transcode: false, } } @@ -350,52 +331,14 @@ impl IMediaProvider for JellyfinMediaProvider { Ok(body.items.into_iter().map(|item| item.name).collect()) } - async fn get_stream_url( - &self, - item_id: &MediaItemId, - quality: &StreamQuality, - ) -> DomainResult { - match quality { - StreamQuality::Direct => { - let url = format!( - "{}/Items/{}/PlaybackInfo", - self.config.base_url, - item_id.as_ref() - ); - let resp = self - .client - .post(&url) - .header("X-Emby-Token", &self.config.api_key) - .query(&[ - ("userId", &self.config.user_id), - ("mediaSourceId", &item_id.as_ref().to_string()), - ]) - .json(&serde_json::json!({})) - .send() - .await - .map_err(|e| { - DomainError::InfrastructureError(format!("PlaybackInfo failed: {e}")) - })?; - - if resp.status().is_success() { - let info: JellyfinPlaybackInfoResponse = resp.json().await.map_err(|e| { - DomainError::InfrastructureError(format!( - "PlaybackInfo parse failed: {e}" - )) - })?; - if let Some(src) = info.media_sources.first() - && src.supports_direct_stream - && let Some(rel_url) = &src.direct_stream_url - { - return Ok(format!( - "{}{}&api_key={}", - self.config.base_url, rel_url, self.config.api_key - )); - } - } - Ok(self.hls_url(item_id, FALLBACK_HLS_BITRATE)) - } - StreamQuality::Transcode(bps) => Ok(self.hls_url(item_id, *bps)), - } + async fn get_source_uri(&self, item_id: &MediaItemId) -> DomainResult { + Ok(SourceUri::NetworkUrl { + url: format!( + "{}/Videos/{}/stream?static=true&api_key={}", + self.config.base_url, + item_id.as_ref(), + self.config.api_key, + ), + }) } } diff --git a/crates/adapters/local-files/src/lib.rs b/crates/adapters/local-files/src/lib.rs index dc0f7e8..ee7099c 100644 --- a/crates/adapters/local-files/src/lib.rs +++ b/crates/adapters/local-files/src/lib.rs @@ -30,7 +30,7 @@ impl LocalFilesBundle { }); let provider = - LocalFilesProvider::new(Arc::clone(&local_index), &config, transcode_manager.clone()); + LocalFilesProvider::new(Arc::clone(&local_index), &config); Self { provider, diff --git a/crates/adapters/local-files/src/provider.rs b/crates/adapters/local-files/src/provider.rs index 8ef9564..8a23006 100644 --- a/crates/adapters/local-files/src/provider.rs +++ b/crates/adapters/local-files/src/provider.rs @@ -1,36 +1,23 @@ use std::sync::Arc; use async_trait::async_trait; -use domain::ports::{ - Collection, IMediaProvider, ProviderCapabilities, StreamQuality, StreamingProtocol, -}; -use domain::{ContentType, DomainError, DomainResult, MediaFilter, MediaItem, MediaItemId, MediaItemRow, MediaRole}; +use domain::ports::{Collection, IMediaProvider, ProviderCapabilities}; +use domain::{ContentType, DomainError, DomainResult, MediaFilter, MediaItem, MediaItemId, MediaItemRow, MediaRole, SourceUri}; use crate::config::LocalFilesConfig; use crate::index::{decode_id, LocalIndex}; use crate::scanner::LocalFileItem; -use crate::transcoder::TranscodeManager; pub struct LocalFilesProvider { pub index: Arc, - base_url: String, - transcode_manager: Option>, } const SHORT_DURATION_SECS: u32 = 1200; const DECADE_SPAN: u16 = 9; impl LocalFilesProvider { - pub fn new( - index: Arc, - config: &LocalFilesConfig, - transcode_manager: Option>, - ) -> Self { - Self { - index, - base_url: config.base_url.trim_end_matches('/').to_string(), - transcode_manager, - } + pub fn new(index: Arc, _config: &LocalFilesConfig) -> Self { + Self { index } } } @@ -73,13 +60,7 @@ impl IMediaProvider for LocalFilesProvider { tags: true, decade: true, search: true, - streaming_protocol: if self.transcode_manager.is_some() { - StreamingProtocol::Hls - } else { - StreamingProtocol::DirectFile - }, rescan: true, - transcode: self.transcode_manager.is_some(), } } @@ -155,31 +136,14 @@ impl IMediaProvider for LocalFilesProvider { .map(|item| to_media_item(item_id.clone(), &item))) } - async fn get_stream_url( - &self, - item_id: &MediaItemId, - quality: &StreamQuality, - ) -> DomainResult { - match quality { - StreamQuality::Transcode(_) if self.transcode_manager.is_some() => { - let tm = self.transcode_manager.as_ref().unwrap(); - let rel = decode_id(item_id).ok_or_else(|| { - DomainError::InfrastructureError("invalid item id encoding".into()) - })?; - let src = self.index.root_dir.join(&rel); - tm.ensure_transcoded(item_id.as_ref(), &src).await?; - Ok(format!( - "{}/api/v1/files/transcode/{}/playlist.m3u8", - self.base_url, - item_id.as_ref() - )) - } - _ => Ok(format!( - "{}/api/v1/files/stream/{}", - self.base_url, - item_id.as_ref() - )), - } + async fn get_source_uri(&self, item_id: &MediaItemId) -> DomainResult { + let rel = decode_id(item_id).ok_or_else(|| { + DomainError::InfrastructureError("invalid item id encoding".into()) + })?; + let abs_path = self.index.root_dir.join(&rel); + Ok(SourceUri::FilePath { + path: abs_path.to_string_lossy().into_owned(), + }) } async fn list_collections(&self) -> DomainResult> { diff --git a/crates/api-types/src/config.rs b/crates/api-types/src/config.rs index 165472c..6a07bc9 100644 --- a/crates/api-types/src/config.rs +++ b/crates/api-types/src/config.rs @@ -1,8 +1,6 @@ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::common::enum_to_string; - #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct ProviderCapabilitiesResponse { pub collections: bool, @@ -11,9 +9,7 @@ pub struct ProviderCapabilitiesResponse { pub tags: bool, pub decade: bool, pub search: bool, - pub streaming_protocol: String, pub rescan: bool, - pub transcode: bool, } impl From for ProviderCapabilitiesResponse { @@ -25,9 +21,7 @@ impl From for ProviderCapabilitiesResponse tags: c.tags, decade: c.decade, search: c.search, - streaming_protocol: enum_to_string(&c.streaming_protocol), rescan: c.rescan, - transcode: c.transcode, } } } diff --git a/crates/application/src/config/get_config.rs b/crates/application/src/config/get_config.rs index 4686533..a02b68b 100644 --- a/crates/application/src/config/get_config.rs +++ b/crates/application/src/config/get_config.rs @@ -41,9 +41,7 @@ pub fn execute(deps: &ConfigDeps, _query: GetConfigQuery) -> SystemConfig { tags: false, decade: false, search: false, - streaming_protocol: domain::ports::StreamingProtocol::DirectFile, rescan: false, - transcode: false, }); SystemConfig { diff --git a/crates/application/src/library/tests/helpers.rs b/crates/application/src/library/tests/helpers.rs index f9eb20d..0d77b8d 100644 --- a/crates/application/src/library/tests/helpers.rs +++ b/crates/application/src/library/tests/helpers.rs @@ -4,10 +4,8 @@ use async_trait::async_trait; use domain::errors::DomainResult; use domain::models::MediaItem; -use domain::ports::{ - Collection, IProviderRegistry, ProviderCapabilities, SeriesSummary, StreamQuality, - StreamingProtocol, -}; +use domain::ports::{Collection, IProviderRegistry, ProviderCapabilities, SeriesSummary}; +use domain::SourceUri; use domain::testing::{InMemoryLibraryRepository, NoopEventPublisher, NoopLibrarySync}; use domain::value_objects::{ContentType, MediaFilter, MediaItemId}; @@ -29,11 +27,7 @@ impl IProviderRegistry for TestProviderRegistry { Ok(None) } - async fn get_stream_url( - &self, - _item_id: &MediaItemId, - _quality: &StreamQuality, - ) -> DomainResult { + async fn get_source_uri(&self, _item_id: &MediaItemId) -> DomainResult { Err(domain::DomainError::InfrastructureError( "TestProviderRegistry does not support streaming".into(), )) @@ -55,9 +49,7 @@ impl IProviderRegistry for TestProviderRegistry { tags: false, decade: false, search: true, - streaming_protocol: StreamingProtocol::Hls, rescan: false, - transcode: false, }) } diff --git a/crates/application/src/schedule/get_stream_url.rs b/crates/application/src/schedule/get_source.rs similarity index 65% rename from crates/application/src/schedule/get_stream_url.rs rename to crates/application/src/schedule/get_source.rs index bdb3eb7..b16d1d4 100644 --- a/crates/application/src/schedule/get_stream_url.rs +++ b/crates/application/src/schedule/get_source.rs @@ -1,13 +1,12 @@ use chrono::Utc; -use domain::ports::StreamQuality; -use domain::value_objects::ChannelId; +use domain::value_objects::{ChannelId, SourceUri}; use domain::{DomainResult, ScheduleEngineService}; use super::deps::ScheduleDeps; -use super::queries::GetStreamUrlQuery; +use super::queries::GetSourceQuery; -pub async fn execute(deps: &ScheduleDeps, query: GetStreamUrlQuery) -> DomainResult> { +pub async fn execute(deps: &ScheduleDeps, query: GetSourceQuery) -> DomainResult> { let channel_id = ChannelId::from(query.channel_id); let now = Utc::now(); @@ -22,9 +21,9 @@ pub async fn execute(deps: &ScheduleDeps, query: GetStreamUrlQuery) -> DomainRes }; let item_id = broadcast.slot().item().id().clone(); - let url = deps + let uri = deps .provider_registry - .get_stream_url(&item_id, &StreamQuality::Direct) + .get_source_uri(&item_id) .await?; - Ok(Some(url)) + Ok(Some(uri)) } diff --git a/crates/application/src/schedule/mod.rs b/crates/application/src/schedule/mod.rs index 0140824..b9820f0 100644 --- a/crates/application/src/schedule/mod.rs +++ b/crates/application/src/schedule/mod.rs @@ -3,9 +3,9 @@ pub mod deps; pub mod generate; pub mod get_current_broadcast; pub mod get_epg; -pub mod get_stream_url; +pub mod get_source; pub mod queries; pub use commands::GenerateScheduleCommand; pub use deps::ScheduleDeps; -pub use queries::{GetCurrentBroadcastQuery, GetEpgQuery, GetStreamUrlQuery}; +pub use queries::{GetCurrentBroadcastQuery, GetEpgQuery, GetSourceQuery}; diff --git a/crates/application/src/schedule/queries.rs b/crates/application/src/schedule/queries.rs index 591e4fd..9fc322a 100644 --- a/crates/application/src/schedule/queries.rs +++ b/crates/application/src/schedule/queries.rs @@ -8,6 +8,6 @@ pub struct GetEpgQuery { pub channel_id: Uuid, } -pub struct GetStreamUrlQuery { +pub struct GetSourceQuery { pub channel_id: Uuid, } diff --git a/crates/application/src/schedule/tests/helpers.rs b/crates/application/src/schedule/tests/helpers.rs index e4e670f..4ab2c9c 100644 --- a/crates/application/src/schedule/tests/helpers.rs +++ b/crates/application/src/schedule/tests/helpers.rs @@ -4,10 +4,8 @@ use async_trait::async_trait; use domain::errors::DomainResult; use domain::models::MediaItem; -use domain::ports::{ - Collection, IProviderRegistry, ProviderCapabilities, - SeriesSummary, StreamQuality, StreamingProtocol, -}; +use domain::ports::{Collection, IProviderRegistry, ProviderCapabilities, SeriesSummary}; +use domain::SourceUri; use domain::testing::{InMemoryChannelRepository, InMemoryLibraryRepository, InMemoryScheduleRepository, NoopEventPublisher}; use domain::value_objects::{ContentType, MediaFilter, MediaItemId}; use domain::ScheduleEngineService; @@ -30,11 +28,7 @@ impl IProviderRegistry for TestProviderRegistry { Ok(None) } - async fn get_stream_url( - &self, - _item_id: &MediaItemId, - _quality: &StreamQuality, - ) -> DomainResult { + async fn get_source_uri(&self, _item_id: &MediaItemId) -> DomainResult { Err(domain::DomainError::InfrastructureError( "TestProviderRegistry does not support streaming".into(), )) @@ -56,9 +50,7 @@ impl IProviderRegistry for TestProviderRegistry { tags: false, decade: false, search: false, - streaming_protocol: StreamingProtocol::Hls, rescan: false, - transcode: false, }) } diff --git a/crates/domain/src/ports/media.rs b/crates/domain/src/ports/media.rs index c39c375..ef7ba54 100644 --- a/crates/domain/src/ports/media.rs +++ b/crates/domain/src/ports/media.rs @@ -3,20 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::errors::{DomainError, DomainResult}; use crate::models::MediaItem; -use crate::value_objects::{ContentType, MediaFilter, MediaItemId}; - -#[derive(Debug, Clone)] -pub enum StreamQuality { - Direct, - Transcode(u32), -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum StreamingProtocol { - Hls, - DirectFile, -} +use crate::value_objects::{ContentType, MediaFilter, MediaItemId, SourceUri}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProviderCapabilities { @@ -26,9 +13,7 @@ pub struct ProviderCapabilities { pub tags: bool, pub decade: bool, pub search: bool, - pub streaming_protocol: StreamingProtocol, pub rescan: bool, - pub transcode: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -55,11 +40,7 @@ pub trait IMediaProvider: Send + Sync { async fn fetch_by_id(&self, item_id: &MediaItemId) -> DomainResult>; - async fn get_stream_url( - &self, - item_id: &MediaItemId, - quality: &StreamQuality, - ) -> DomainResult; + async fn get_source_uri(&self, item_id: &MediaItemId) -> DomainResult; async fn list_collections(&self) -> DomainResult> { Err(DomainError::InfrastructureError( @@ -95,11 +76,7 @@ pub trait IProviderRegistry: Send + Sync { async fn fetch_by_id(&self, item_id: &MediaItemId) -> DomainResult>; - async fn get_stream_url( - &self, - item_id: &MediaItemId, - quality: &StreamQuality, - ) -> DomainResult; + async fn get_source_uri(&self, item_id: &MediaItemId) -> DomainResult; fn provider_ids(&self) -> Vec; diff --git a/crates/domain/src/ports/mod.rs b/crates/domain/src/ports/mod.rs index c296ed4..3818162 100644 --- a/crates/domain/src/ports/mod.rs +++ b/crates/domain/src/ports/mod.rs @@ -17,7 +17,6 @@ pub use events::{DomainEvent, EventConsumer, EventEnvelope, EventHandler, EventP pub use library::{LibraryCommand, LibraryQuery, LibrarySyncAdapter}; pub use media::{ Collection, IMediaProvider, IProviderRegistry, ProviderCapabilities, SeriesSummary, - StreamQuality, StreamingProtocol, }; pub use provider_config::{ProviderConfigCommand, ProviderConfigQuery}; pub use schedule::{ScheduleCommand, ScheduleQuery}; diff --git a/crates/domain/src/testing/noops.rs b/crates/domain/src/testing/noops.rs index 3ec6ca1..3910f8a 100644 --- a/crates/domain/src/testing/noops.rs +++ b/crates/domain/src/testing/noops.rs @@ -7,9 +7,9 @@ use crate::models::{ }; use crate::ports::{ ActivityLogCommand, ActivityLogQuery, EventConsumer, EventPublisher, IMediaProvider, - LibrarySyncAdapter, ProviderCapabilities, StreamQuality, StreamingProtocol, + LibrarySyncAdapter, ProviderCapabilities, }; -use crate::value_objects::{ChannelId, MediaFilter, MediaItemId}; +use crate::value_objects::{ChannelId, MediaFilter, MediaItemId, SourceUri}; pub struct NoopEventPublisher; @@ -85,9 +85,7 @@ impl IMediaProvider for NoopMediaProvider { tags: false, decade: false, search: false, - streaming_protocol: StreamingProtocol::Hls, rescan: false, - transcode: false, } } @@ -99,11 +97,7 @@ impl IMediaProvider for NoopMediaProvider { Ok(None) } - async fn get_stream_url( - &self, - _item_id: &MediaItemId, - _quality: &StreamQuality, - ) -> DomainResult { + async fn get_source_uri(&self, _item_id: &MediaItemId) -> DomainResult { Err(crate::errors::DomainError::InfrastructureError( "NoopMediaProvider does not support streaming".into(), )) diff --git a/crates/domain/src/value_objects/mod.rs b/crates/domain/src/value_objects/mod.rs index cb88edc..250eb57 100644 --- a/crates/domain/src/value_objects/mod.rs +++ b/crates/domain/src/value_objects/mod.rs @@ -3,9 +3,11 @@ pub mod channel; pub mod ids; pub mod scheduling; pub mod search; +pub mod streaming; pub use auth::*; pub use channel::*; pub use ids::*; pub use scheduling::*; pub use search::*; +pub use streaming::*; diff --git a/crates/domain/src/value_objects/streaming.rs b/crates/domain/src/value_objects/streaming.rs new file mode 100644 index 0000000..aad31df --- /dev/null +++ b/crates/domain/src/value_objects/streaming.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum SourceUri { + NetworkUrl { url: String }, + FilePath { path: String }, +} diff --git a/crates/mcp/src/main.rs b/crates/mcp/src/main.rs index 01560b6..533b5d1 100644 --- a/crates/mcp/src/main.rs +++ b/crates/mcp/src/main.rs @@ -1,7 +1,6 @@ use std::sync::Arc; -use domain::ports::{IMediaProvider, IProviderRegistry, ProviderCapabilities, StreamingProtocol}; -use domain::ports::StreamQuality; +use domain::ports::{IMediaProvider, IProviderRegistry, ProviderCapabilities}; use domain::{DomainError, DomainResult, MediaFilter, MediaItem, MediaItemId, ScheduleEngineService}; use infra_wiring::DbPool; use tracing::info; @@ -175,9 +174,7 @@ impl IMediaProvider for NoopMediaProvider { tags: false, decade: false, search: false, - streaming_protocol: StreamingProtocol::DirectFile, rescan: false, - transcode: false, } } @@ -193,7 +190,7 @@ impl IMediaProvider for NoopMediaProvider { )) } - async fn get_stream_url(&self, _: &MediaItemId, _: &StreamQuality) -> DomainResult { + async fn get_source_uri(&self, _: &MediaItemId) -> DomainResult { Err(DomainError::InfrastructureError( "No media provider configured.".into(), )) @@ -254,19 +251,18 @@ impl IProviderRegistry for SimpleProviderRegistry { } } - async fn get_stream_url( + async fn get_source_uri( &self, item_id: &MediaItemId, - quality: &StreamQuality, - ) -> DomainResult { + ) -> DomainResult { let id_str = item_id.value(); if let Some(pid) = Self::extract_provider_id(id_str) && 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(), diff --git a/crates/presentation/src/factory.rs b/crates/presentation/src/factory.rs index d6cf3c5..09c40a6 100644 --- a/crates/presentation/src/factory.rs +++ b/crates/presentation/src/factory.rs @@ -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 { + async fn get_source_uri(&self, _: &domain::MediaItemId) -> domain::DomainResult { 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 { + ) -> domain::DomainResult { 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(), diff --git a/crates/presentation/src/handlers/schedule.rs b/crates/presentation/src/handlers/schedule.rs index 6497f49..5b663af 100644 --- a/crates/presentation/src/handlers/schedule.rs +++ b/crates/presentation/src/handlers/schedule.rs @@ -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, Path(id): Path, ) -> Result { - 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()), } } diff --git a/crates/worker/src/jobs/library_sync.rs b/crates/worker/src/jobs/library_sync.rs index 8ea28a5..a18752b 100644 --- a/crates/worker/src/jobs/library_sync.rs +++ b/crates/worker/src/jobs/library_sync.rs @@ -86,9 +86,7 @@ impl domain::ports::IMediaProvider for RegistryProviderAdapter { tags: false, decade: false, search: false, - streaming_protocol: domain::ports::StreamingProtocol::DirectFile, rescan: false, - transcode: false, }) } @@ -106,11 +104,10 @@ impl domain::ports::IMediaProvider for RegistryProviderAdapter { self.registry.fetch_by_id(item_id).await } - async fn get_stream_url( + async fn get_source_uri( &self, item_id: &domain::MediaItemId, - quality: &domain::ports::StreamQuality, - ) -> domain::DomainResult { - self.registry.get_stream_url(item_id, quality).await + ) -> domain::DomainResult { + self.registry.get_source_uri(item_id).await } } diff --git a/crates/worker/src/main.rs b/crates/worker/src/main.rs index e853100..bd62567 100644 --- a/crates/worker/src/main.rs +++ b/crates/worker/src/main.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use application::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}; @@ -170,9 +170,7 @@ impl IMediaProvider for NoopMediaProvider { tags: false, decade: false, search: false, - streaming_protocol: StreamingProtocol::DirectFile, rescan: false, - transcode: false, } } @@ -194,11 +192,10 @@ impl IMediaProvider for NoopMediaProvider { )) } - async fn get_stream_url( + async fn get_source_uri( &self, _: &domain::MediaItemId, - _: &domain::ports::StreamQuality, - ) -> domain::DomainResult { + ) -> domain::DomainResult { Err(DomainError::InfrastructureError( "No media provider configured.".into(), )) @@ -262,19 +259,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 { + ) -> domain::DomainResult { 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(),