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

@@ -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,

View File

@@ -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<LocalIndex>,
base_url: String,
transcode_manager: Option<Arc<TranscodeManager>>,
}
const SHORT_DURATION_SECS: u32 = 1200;
const DECADE_SPAN: u16 = 9;
impl LocalFilesProvider {
pub fn new(
index: Arc<LocalIndex>,
config: &LocalFilesConfig,
transcode_manager: Option<Arc<TranscodeManager>>,
) -> Self {
Self {
index,
base_url: config.base_url.trim_end_matches('/').to_string(),
transcode_manager,
}
pub fn new(index: Arc<LocalIndex>, _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<String> {
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<SourceUri> {
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<Vec<Collection>> {