# Jellyfin SRF Play Plugin A Jellyfin plugin for accessing SRF Play (Swiss Radio and Television) video-on-demand content and live sports streaming. ## Status **Beta/Alpha** - This plugin has been tested on two Jellyfin instances and is working. Some clients may experience issues with hardware decoding, which appears to be client-specific behavior. ## Quick Install Add this repository URL in Jellyfin (Dashboard → Plugins → Repositories): ``` https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/raw/branch/master/manifest.json ``` Then install "SRF Play" from the plugin catalog. ## Features - Access to SRF Play VOD content (video-on-demand, no DRM-protected content) - **Live Sports Streaming** - Watch scheduled sports events (skiing, Formula 1, football, tennis, etc.) - Support for all Swiss broadcasting units (SRF, RTS, RSI, RTR, SWI) - Automatic content expiration handling - Latest and trending content discovery - Quality selection (Auto lets CDN decide, SD prefers 480p/360p, HD prefers 1080p/720p) - HLS streaming support with Akamai token authentication - Proxy support for routing traffic through alternate gateways - Smart caching with reduced TTL for upcoming livestreams ## Screenshots ### Channel Menu ![SRF Play Menu](res/menu.png) The main channel interface showing the content folders. ### Playback ![SRF Play Playback](res/playback.png) Video playback with HLS streaming support and quality selection. ## Testing The plugin includes comprehensive testing: - **Unit tests** (xUnit framework) for core services - **API spec validation tests** for all business units (SRF, RTS, RSI, RTR, SWI) - **Integration testing** - Tested on multiple Jellyfin instances with VOD and live sports streaming **Run tests:** ```bash # All tests dotnet test # Unit tests only dotnet test --filter "Category!=Integration&Category!=APISpec" # API spec tests only dotnet test --filter "Category=APISpec" # With coverage dotnet test --collect:"XPlat Code Coverage" ``` See [Test Documentation](Jellyfin.Plugin.SRFPlay.Tests/README.md) for more details. ## API Information **Base URL:** `https://il.srgssr.ch/integrationlayer/2.0/` ### Key Endpoints **Integration Layer API v2.0:** - `GET /mediaComposition/byUrn/{urn}` - Get video metadata and playable URLs - `GET /video/{businessUnit}/latest` - Get latest videos - `GET /video/{businessUnit}/trending` - Get trending videos **Play v3 API:** - `GET /play/v3/api/{bu}/production/livestreams?eventType=SPORT` - Get scheduled sports livestreams - `GET /play/v3/api/{bu}/production/shows/{id}` - Get show details - `GET /play/v3/api/{bu}/production/videos-by-show-id?showId={id}` - Get episodes for a show **Akamai Token Service:** - `GET /akahd/token?acl=/{path}/*` - Authenticate stream URLs for playback ### URN Format - `urn:{bu}:video:{id}` - Video URN (VOD content) - `urn:{bu}:scheduled_livestream:video:{id}` - Scheduled sports livestream URN (supported) - `urn:{bu}:video:livestream_{channel}` - Live TV channel URN (not supported due to Widevine DRM) Examples: - `urn:srf:video:c4927fcf-4ab4-4bcf-be4e-00e4ee9e6d6b` (VOD) - `urn:srf:scheduled_livestream:video:7b31c9a6-a96e-4c0e-bfc2-f0d6237f2233` (Sports event) - `urn:srf:video:c4927fcf-e1a0-0001-7edd-1ef01d441651` (SRF 1 Live - DRM protected) ## Building the Plugin ### Prerequisites - .NET 8.0 SDK - Jellyfin 10.9.11 or later ### Build Steps ```bash cd Jellyfin.Plugin.SRFPlay dotnet build ``` ### Output The compiled plugin will be in `bin/Debug/net8.0/` ## Manual Installation 1. Build the plugin (see above) 2. Copy the compiled DLL to your Jellyfin plugins directory 3. Restart Jellyfin 4. Configure the plugin in Jellyfin Dashboard → Plugins → SRF Play 5. The SRF Play channel will appear in Jellyfin with three main folders: - **Latest Videos** - Recently published content - **Trending Videos** - Popular content - **Live Sports & Events** - Scheduled sports livestreams (skiing, F1, football, etc.) ## Configuration - **Business Unit**: Select the Swiss broadcasting unit (default: SRF) - **Quality Preference**: Choose video quality — Auto (first available, CDN decides), SD, or HD - **Content Refresh Interval**: How often to check for new content (1-168 hours) - **Expiration Check Interval**: How often to check for expired content (1-168 hours) - **Cache Duration**: How long to cache metadata (5-1440 minutes) - **Enable Latest Content**: Automatically discover latest videos - **Enable Trending Content**: Automatically discover trending videos - **Proxy Settings**: Configure proxy server for routing SRF API traffic (optional) - **Use Proxy**: Enable/disable proxy usage - **Proxy Address**: Proxy server URL (e.g., http://proxy.example.com:8080) - **Proxy Username**: Optional authentication username - **Proxy Password**: Optional authentication password For detailed proxy setup instructions, see [PROXY_SETUP_GUIDE.md](PROXY_SETUP_GUIDE.md). ## Troubleshooting If you encounter issues with the plugin: - Check [DEBUG_GUIDE.md](DEBUG_GUIDE.md) for detailed logging information - Enable debug logging in Jellyfin to see detailed request/response information - Common issues include DRM-protected content and geo-restrictions ## Technical Architecture ### Directory Structure ``` Jellyfin.Plugin.SRFPlay/ ├── Api/ │ ├── Models/ # API response models │ │ ├── MediaComposition.cs # Root composition (HasChapters helper) │ │ ├── Chapter.cs # Video/episode chapter with resources │ │ ├── Resource.cs # Stream URL entry (IsPlayable helper) │ │ ├── Show.cs │ │ ├── Episode.cs │ │ └── PlayV3/ # Play v3 API models │ │ ├── PlayV3Show.cs │ │ ├── PlayV3Topic.cs │ │ ├── PlayV3Video.cs │ │ ├── PlayV3TvProgram.cs │ │ ├── PlayV3Response.cs │ │ ├── PlayV3DirectResponse.cs │ │ └── PlayV3DataContainer.cs │ ├── SRFApiClient.cs # HTTP client for SRF APIs │ ├── ISRFApiClientFactory.cs # Factory interface │ └── SRFApiClientFactory.cs # Factory implementation ├── Channels/ │ └── SRFPlayChannel.cs # Channel implementation ├── Configuration/ │ ├── PluginConfiguration.cs │ └── configPage.html ├── Constants/ │ └── ApiEndpoints.cs # API base URLs and endpoint constants ├── Controllers/ │ └── StreamProxyController.cs # HLS proxy endpoints (master/variant/segment) ├── Services/ │ ├── Interfaces/ # Service contracts │ │ ├── IStreamProxyService.cs │ │ ├── IStreamUrlResolver.cs │ │ ├── IMediaCompositionFetcher.cs │ │ ├── IMediaSourceFactory.cs │ │ ├── IMetadataCache.cs │ │ ├── IContentRefreshService.cs │ │ ├── IContentExpirationService.cs │ │ └── ICategoryService.cs │ ├── StreamProxyService.cs # HLS proxy: auth, manifest rewriting, segments │ ├── StreamUrlResolver.cs # Stream selection & Akamai authentication │ ├── MediaCompositionFetcher.cs # Cached API fetcher │ ├── MediaSourceFactory.cs # Jellyfin MediaSourceInfo builder │ ├── MetadataCache.cs # Thread-safe ConcurrentDictionary cache │ ├── ContentExpirationService.cs │ ├── ContentRefreshService.cs │ └── CategoryService.cs ├── Providers/ │ ├── SRFSeriesProvider.cs # Series metadata │ ├── SRFEpisodeProvider.cs # Episode metadata │ ├── SRFImageProvider.cs # Image fetching │ └── SRFMediaProvider.cs # Playback URLs ├── Utilities/ │ ├── Extensions.cs # BusinessUnit.ToLowerString() extension │ ├── MimeTypeHelper.cs # Content-type detection │ ├── PlaceholderImageGenerator.cs │ └── UrnHelper.cs # URN parsing utilities ├── ScheduledTasks/ │ ├── ContentRefreshTask.cs # Periodic content refresh │ └── ExpirationCheckTask.cs # Periodic expiration check ├── ServiceRegistrator.cs # DI registration └── Plugin.cs # Main plugin entry point ``` ### Key Components 1. **API Client** (`SRFApiClient`): HTTP requests to SRF Integration Layer and Play v3 API, with proxy support 2. **Channel** (`SRFPlayChannel`): SRF Play channel with Latest, Trending, and Live Sports folders 3. **Stream Proxy** (`StreamProxyService` + `StreamProxyController`): HLS proxy that handles Akamai token auth, manifest URL rewriting, deferred authentication, and token refresh for both VOD and livestreams 4. **Stream Resolver** (`StreamUrlResolver`): Selects optimal HLS stream by quality preference, filters DRM content 5. **Metadata Cache** (`MetadataCache`): Thread-safe `ConcurrentDictionary` cache with dynamic TTL for livestreams 6. **Media Composition Fetcher** (`MediaCompositionFetcher`): Cached wrapper around API client for media composition requests 7. **Content Providers** (`SRFSeriesProvider`, `SRFEpisodeProvider`, `SRFImageProvider`, `SRFMediaProvider`): Jellyfin integration for series, episodes, images, and media sources 8. **Scheduled Tasks**: Automatic content refresh and expiration management 9. **Utilities**: Business unit extensions, MIME type helpers, URN parsing, placeholder image generation ## Important Notes ### Content Limitations - **No DRM content**: Only non-DRM protected content is accessible (no Widevine/FairPlay) - **No live TV channels**: Main channels (SRF 1, SRF zwei, SRF info) use DRM and are not supported - **Sports livestreams supported**: Scheduled sports events (skiing, F1, football, etc.) work without DRM - **Content expiration**: SRF content has validity periods, plugin tracks and removes expired content - **Upcoming events**: Sports events appear before they start but require channel refresh to play once live - **No subtitles**: Subtitle support not currently implemented ### Extensibility The plugin is designed to support all Swiss broadcasting units: - **SRF** (Schweizer Radio und Fernsehen - German) - **RTS** (Radio Télévision Suisse - French) - **RSI** (Radiotelevisione svizzera - Italian) - **RTR** (Radiotelevisiun Svizra Rumantscha - Romansh) - **SWI** (Swiss World International) Currently focused on SRF but easily extensible. ## Development The plugin includes: - Complete API integration with SRF Play (Integration Layer v2.0 and Play v3) - **Live sports streaming** with scheduled event detection - Channel with Latest, Trending, and Live Sports folders - Metadata providers for series and episodes - Image fetching and caching - HLS stream playback with Akamai token authentication - Automatic content expiration handling - Scheduled tasks for content refresh - Smart caching with dynamic TTL for upcoming livestreams ### Known Issues - Some clients may experience issues with hardware decoding (appears to be client-specific) - Some edge cases may need additional handling - Performance optimization may be needed for very large content catalogs ### Contributing Contributions welcome! ## License See LICENSE file for details. ## Acknowledgments This plugin was developed partly using [Claude Code](https://docs.anthropic.com/en/docs/claude-code) by Anthropic. Inspired by the excellent [Kodi SRG SSR addon](https://github.com/goggle/script.module.srgssr) by [@goggle](https://github.com/goggle), which served as a fantastic reference for understanding the SRG SSR API structure, authentication mechanisms, and handling of scheduled livestreams. ## References - [SRF Play](https://www.srf.ch/play) - [Jellyfin Plugin Documentation](https://jellyfin.org/docs/general/server/plugins/) - [SRG SSR Integration Layer API](https://il.srgssr.ch/) - [Kodi SRG SSR Addon](https://github.com/goggle/script.module.srgssr) - Reference implementation