working livestreams!
🏗️ Build Plugin / build (push) Successful in 3m27s
🧪 Test Plugin / test (push) Successful in 1m38s
🚀 Release Plugin / build-and-release (push) Successful in 3m29s

This commit is contained in:
2025-11-22 14:14:43 +01:00
parent b8ac466c90
commit 89a911b9c4
5 changed files with 268 additions and 54 deletions
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -26,6 +27,7 @@ public class SRFMediaProvider : IMediaSourceProvider
private readonly StreamUrlResolver _streamResolver;
private readonly StreamProxyService _proxyService;
private readonly IServerApplicationHost _appHost;
private readonly Dictionary<string, string> _openTokenToItemId = new();
/// <summary>
/// Initializes a new instance of the <see cref="SRFMediaProvider"/> class.
@@ -184,10 +186,17 @@ 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
// Create proxy URL as absolute HTTP URL (required for ffmpeg)
// Use the actual server URL so remote clients can access it
// Include item ID as query parameter to preserve it during transcoding
var proxyUrl = $"{serverUrl}/Plugins/SRFPlay/Proxy/{itemIdStr}/master.m3u8?itemId={itemIdStr}";
// Detect if this is a live stream
var isLiveStream = chapter.Type == "SCHEDULED_LIVESTREAM" || urn.Contains("livestream", StringComparison.OrdinalIgnoreCase);
// 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}";
_logger.LogInformation(
"Using proxy URL for item {ItemId}: {ProxyUrl} (PublicServerUrl configured: {IsPublicConfigured})",
@@ -195,9 +204,6 @@ public class SRFMediaProvider : IMediaSourceProvider
proxyUrl,
!string.IsNullOrWhiteSpace(config.PublicServerUrl));
// Detect if this is a live stream
var isLiveStream = chapter.Type == "SCHEDULED_LIVESTREAM" || urn.Contains("livestream", StringComparison.OrdinalIgnoreCase);
// Create media source using proxy URL - enables DirectPlay!
var mediaSource = new MediaSourceInfo
{
@@ -214,10 +220,11 @@ public class SRFMediaProvider : IMediaSourceProvider
RunTimeTicks = chapter.Duration > 0 ? TimeSpan.FromMilliseconds(chapter.Duration).Ticks : null,
VideoType = VideoType.VideoFile,
IsInfiniteStream = isLiveStream, // True for live streams!
RequiresOpening = false,
RequiresOpening = true, // Enable to handle transcoding sessions
RequiresClosing = false,
SupportsProbing = false, // Disable probing for proxy URLs
ReadAtNativeFramerate = isLiveStream, // Read at native framerate for live streams
OpenToken = openToken, // Token to identify this media source
MediaStreams = new List<MediaBrowser.Model.Entities.MediaStream>
{
new MediaBrowser.Model.Entities.MediaStream
@@ -281,10 +288,27 @@ public class SRFMediaProvider : IMediaSourceProvider
}
/// <inheritdoc />
public Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
public async Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
_logger.LogWarning("OpenMediaSource called with openToken: {OpenToken} - This should not be called for HTTP streams!", openToken);
// Not needed for static HTTP streams
throw new NotImplementedException();
_logger.LogInformation("OpenMediaSource called with openToken: {OpenToken}", openToken);
// Look up the original item ID from the open token
if (!_openTokenToItemId.TryGetValue(openToken, out var originalItemId))
{
_logger.LogError("Open token {OpenToken} not found in registry", openToken);
throw new InvalidOperationException($"Open token {openToken} not found");
}
_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,
_loggerFactory);
return await Task.FromResult<ILiveStream>(liveStream).ConfigureAwait(false);
}
}