jellyfin-srfPlay/README.md
Duncan Tourolle 7c76402a4a
Some checks failed
🏗️ Build Plugin / build (push) Failing after 9s
🧪 Test Plugin / test (push) Successful in 1m28s
🚀 Release Plugin / build-and-release (push) Failing after 5s
Clean up dead code, consolidate duplication, fix redundancies
Remove 9 dead methods, 6 unused constants, and redundant
ReaderWriterLockSlim from MetadataCache. Consolidate repeated
patterns into HasChapters, IsPlayable, and ToLowerString helpers.
Extract shared API methods in SRFApiClient. Move variant manifest
rewriting from controller to StreamProxyService. Make Auto quality
distinct from HD. Update README architecture section.
2026-02-28 11:34:45 +01:00

291 lines
12 KiB
Markdown

# 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