From 4f9ebe2bce1e197d2aeec5f686183cac0824d759 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 6 Dec 2025 18:08:34 +0100 Subject: [PATCH] enable query and provide stream assumed stream info if missing. --- .../Providers/SRFMediaProvider.cs | 107 +++++++++--------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/Jellyfin.Plugin.SRFPlay/Providers/SRFMediaProvider.cs b/Jellyfin.Plugin.SRFPlay/Providers/SRFMediaProvider.cs index 1cfd52a..d89d76c 100644 --- a/Jellyfin.Plugin.SRFPlay/Providers/SRFMediaProvider.cs +++ b/Jellyfin.Plugin.SRFPlay/Providers/SRFMediaProvider.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.SRFPlay.Configuration; @@ -22,12 +20,10 @@ namespace Jellyfin.Plugin.SRFPlay.Providers; public class SRFMediaProvider : IMediaSourceProvider { private readonly ILogger _logger; - private readonly ILoggerFactory _loggerFactory; private readonly IMediaCompositionFetcher _compositionFetcher; private readonly IStreamUrlResolver _streamResolver; private readonly IStreamProxyService _proxyService; private readonly IServerApplicationHost _appHost; - private readonly Dictionary _openTokenToItemId = new(); /// /// Initializes a new instance of the class. @@ -44,7 +40,6 @@ public class SRFMediaProvider : IMediaSourceProvider IStreamProxyService proxyService, IServerApplicationHost appHost) { - _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); _compositionFetcher = compositionFetcher; _streamResolver = streamResolver; @@ -172,14 +167,8 @@ public class SRFMediaProvider : IMediaSourceProvider ? config.PublicServerUrl.TrimEnd('/') // Use configured public URL (important for Android/remote clients) : _appHost.GetSmartApiUrl(string.Empty); // Fall back to Jellyfin's smart URL resolution - // Generate an open token for this media source (used to track transcoding sessions) - var openToken = Guid.NewGuid().ToString("N"); - _openTokenToItemId[openToken] = itemIdStr; - _logger.LogDebug("Created open token {OpenToken} for item {ItemId}", openToken, itemIdStr); - - // Create proxy URL using token instead of item ID in path - // This prevents Jellyfin from rewriting the URL during transcoding - var proxyUrl = $"{serverUrl}/Plugins/SRFPlay/Proxy/{itemIdStr}/master.m3u8?token={openToken}"; + // Create proxy URL - item ID is all we need since proxy handles auth + var proxyUrl = $"{serverUrl}/Plugins/SRFPlay/Proxy/{itemIdStr}/master.m3u8"; _logger.LogInformation( "Using proxy URL for item {ItemId}: {ProxyUrl} (PublicServerUrl configured: {IsPublicConfigured})", @@ -197,36 +186,17 @@ public class SRFMediaProvider : IMediaSourceProvider Container = "hls", SupportsDirectStream = true, SupportsDirectPlay = true, // ✅ Enabled! Proxy handles auth - SupportsTranscoding = true, + SupportsTranscoding = false, // Prefer DirectPlay - no transcoding needed for HLS IsRemote = false, // False because it's a local proxy endpoint Type = MediaSourceType.Default, RunTimeTicks = chapter.Duration > 0 ? TimeSpan.FromMilliseconds(chapter.Duration).Ticks : null, VideoType = VideoType.VideoFile, IsInfiniteStream = isLiveStream, // True for live streams! - RequiresOpening = true, // Enable to handle transcoding sessions + RequiresOpening = false, // Proxy handles auth - no need for OpenMediaSource RequiresClosing = false, - SupportsProbing = false, // Disable probing for proxy URLs + SupportsProbing = true, // Enable probing so Jellyfin can verify stream compatibility ReadAtNativeFramerate = isLiveStream, // Read at native framerate for live streams - OpenToken = openToken, // Token to identify this media source - MediaStreams = new List - { - new MediaBrowser.Model.Entities.MediaStream - { - Type = MediaStreamType.Video, - Codec = "h264", - Profile = "high", - IsInterlaced = false, - IsDefault = true, - Index = 0 - }, - new MediaBrowser.Model.Entities.MediaStream - { - Type = MediaStreamType.Audio, - Codec = "aac", - IsDefault = true, - Index = 1 - } - } + MediaStreams = CreateMediaStreams(qualityPref) }; sources.Add(mediaSource); @@ -271,26 +241,57 @@ public class SRFMediaProvider : IMediaSourceProvider } /// - public async Task OpenMediaSource(string openToken, List currentLiveStreams, CancellationToken cancellationToken) + public Task OpenMediaSource(string openToken, List currentLiveStreams, CancellationToken cancellationToken) { - _logger.LogInformation("OpenMediaSource called with openToken: {OpenToken}", openToken); + // Not used - RequiresOpening is false, proxy handles authentication directly + _logger.LogWarning("OpenMediaSource unexpectedly called with openToken: {OpenToken}", openToken); + throw new NotSupportedException("OpenMediaSource not supported - streams use direct proxy access"); + } - // Look up the original item ID from the open token - if (!_openTokenToItemId.TryGetValue(openToken, out var originalItemId)) + /// + /// Creates MediaStream metadata based on quality preference. + /// These are approximate values - HLS adaptive streaming will use actual stream qualities. + /// + private static List CreateMediaStreams(QualityPreference quality) + { + // Set resolution/bitrate based on quality preference + // These are typical values for SRF streams - actual HLS will adapt + var (width, height, videoBitrate) = quality switch { - _logger.LogError("Open token {OpenToken} not found in registry", openToken); - throw new InvalidOperationException($"Open token {openToken} not found"); - } + QualityPreference.SD => (1280, 720, 2500000), + QualityPreference.HD => (1920, 1080, 5000000), + _ => (1280, 720, 3000000) + }; - _logger.LogInformation("Open token {OpenToken} maps to original item ID: {ItemId}", openToken, originalItemId); - - // Create a live stream wrapper - var liveStream = new SRFLiveStream( - _logger, - _proxyService, - originalItemId, - openToken); - - return await Task.FromResult(liveStream).ConfigureAwait(false); + return new List + { + new MediaBrowser.Model.Entities.MediaStream + { + Type = MediaStreamType.Video, + Codec = "h264", + Profile = "high", + Level = 40, + Width = width, + Height = height, + BitRate = videoBitrate, + BitDepth = 8, + IsInterlaced = false, + IsDefault = true, + Index = 0, + IsAVC = true, + PixelFormat = "yuv420p" + }, + new MediaBrowser.Model.Entities.MediaStream + { + Type = MediaStreamType.Audio, + Codec = "aac", + Profile = "LC", + Channels = 2, + SampleRate = 48000, + BitRate = 128000, + IsDefault = true, + Index = 1 + } + }; } }