feat: implement multi-provider support in media library

- Introduced IProviderRegistry to manage multiple media providers.
- Updated AppState to use provider_registry instead of a single media_provider.
- Refactored library routes to support provider-specific queries for collections, series, genres, and items.
- Enhanced ProgrammingBlock to include provider_id for algorithmic and manual content types.
- Modified frontend components to allow selection of providers and updated API calls to include provider parameters.
- Adjusted hooks and types to accommodate provider-specific functionality.
This commit is contained in:
2026-03-14 23:59:21 +01:00
parent c53892159a
commit ead65e6be2
21 changed files with 468 additions and 150 deletions

View File

@@ -2,8 +2,9 @@ use std::sync::Arc;
use std::time::Duration as StdDuration;
use domain::{
ChannelService, DomainError, DomainResult, IMediaProvider, MediaFilter, MediaItemId,
ProviderCapabilities, ScheduleEngineService, StreamQuality, StreamingProtocol, UserService,
ChannelService, DomainError, DomainResult, IMediaProvider, IProviderRegistry, MediaFilter,
MediaItemId, ProviderCapabilities, ScheduleEngineService, StreamQuality, StreamingProtocol,
UserService,
};
use infra::factory::{build_channel_repository, build_schedule_repository, build_user_repository};
use infra::run_migrations;
@@ -69,7 +70,7 @@ async fn main() -> anyhow::Result<()> {
let _user_service = UserService::new(user_repo);
let channel_service = ChannelService::new(channel_repo.clone());
let mut maybe_provider: Option<Arc<dyn IMediaProvider>> = None;
let mut registry = infra::ProviderRegistry::new();
#[cfg(feature = "jellyfin")]
{
@@ -78,41 +79,34 @@ async fn main() -> anyhow::Result<()> {
let user_id = std::env::var("JELLYFIN_USER_ID").ok();
if let (Some(base_url), Some(api_key), Some(user_id)) = (base_url, api_key, user_id) {
info!("Media provider: Jellyfin at {}", base_url);
maybe_provider = Some(Arc::new(infra::JellyfinMediaProvider::new(
infra::JellyfinConfig {
base_url,
api_key,
user_id,
},
registry.register("jellyfin", Arc::new(infra::JellyfinMediaProvider::new(
infra::JellyfinConfig { base_url, api_key, user_id },
)));
}
}
#[cfg(feature = "local-files")]
if maybe_provider.is_none() {
if let Some(dir) = std::env::var("LOCAL_FILES_DIR").ok().map(std::path::PathBuf::from) {
if let k_core::db::DatabasePool::Sqlite(ref sqlite_pool) = db_pool {
let base_url = std::env::var("BASE_URL")
.unwrap_or_else(|_| "http://localhost:3000".to_string());
let lf_cfg = infra::LocalFilesConfig {
root_dir: dir,
base_url,
};
let idx = Arc::new(infra::LocalIndex::new(&lf_cfg, sqlite_pool.clone()).await);
let scan_idx = Arc::clone(&idx);
tokio::spawn(async move { scan_idx.rescan().await; });
maybe_provider = Some(Arc::new(infra::LocalFilesProvider::new(idx, lf_cfg)));
}
if let Some(dir) = std::env::var("LOCAL_FILES_DIR").ok().map(std::path::PathBuf::from) {
if let k_core::db::DatabasePool::Sqlite(ref sqlite_pool) = db_pool {
let base_url = std::env::var("BASE_URL")
.unwrap_or_else(|_| "http://localhost:3000".to_string());
let lf_cfg = infra::LocalFilesConfig { root_dir: dir, base_url };
let idx = Arc::new(infra::LocalIndex::new(&lf_cfg, sqlite_pool.clone()).await);
let scan_idx = Arc::clone(&idx);
tokio::spawn(async move { scan_idx.rescan().await; });
registry.register("local", Arc::new(infra::LocalFilesProvider::new(idx, lf_cfg)));
}
}
let media_provider: Arc<dyn IMediaProvider> = maybe_provider.unwrap_or_else(|| {
if registry.is_empty() {
tracing::warn!("No media provider configured. Set JELLYFIN_BASE_URL or LOCAL_FILES_DIR.");
Arc::new(NoopMediaProvider)
});
registry.register("noop", Arc::new(NoopMediaProvider));
}
let registry = Arc::new(registry);
let schedule_engine = ScheduleEngineService::new(
Arc::clone(&media_provider),
Arc::clone(&registry) as Arc<dyn IProviderRegistry>,
channel_repo,
schedule_repo,
);
@@ -120,7 +114,7 @@ async fn main() -> anyhow::Result<()> {
let server = KTvMcpServer {
channel_service: Arc::new(channel_service),
schedule_engine: Arc::new(schedule_engine),
media_provider,
provider_registry: registry,
owner_id,
};