diff --git a/Jellyfin.Plugin.SRFPlay/Channels/SRFPlayChannel.cs b/Jellyfin.Plugin.SRFPlay/Channels/SRFPlayChannel.cs
index 23bb333..5e63e77 100644
--- a/Jellyfin.Plugin.SRFPlay/Channels/SRFPlayChannel.cs
+++ b/Jellyfin.Plugin.SRFPlay/Channels/SRFPlayChannel.cs
@@ -61,7 +61,7 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
public string Description => "Swiss Radio and Television video-on-demand content";
///
- public string DataVersion => "1.0";
+ public string DataVersion => "1.1";
///
public string HomePageUrl => "https://www.srf.ch/play";
@@ -433,36 +433,34 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
// Generate deterministic GUID from URN
var itemId = UrnToGuid(urn);
- // Get stream URL and authenticate it
+ // Get stream URL (unauthenticated - token will be added at playback time by SRFMediaProvider)
var streamUrl = _streamResolver.GetStreamUrl(chapter, config.QualityPreference);
- // For scheduled livestreams that haven't started, streamUrl might be null
- var isUpcomingLivestream = chapter.Type == "SCHEDULED_LIVESTREAM" && string.IsNullOrEmpty(streamUrl);
-
- if (!string.IsNullOrEmpty(streamUrl))
+ // Skip scheduled livestreams that haven't started yet (no stream URL available)
+ if (chapter.Type == "SCHEDULED_LIVESTREAM" && string.IsNullOrEmpty(streamUrl))
{
- // Authenticate the stream URL (required for all SRF streams, especially livestreams)
- streamUrl = await _streamResolver.GetAuthenticatedStreamUrlAsync(streamUrl, cancellationToken).ConfigureAwait(false);
- }
- else if (isUpcomingLivestream)
- {
- // Use a placeholder for upcoming events
- streamUrl = "http://placeholder.local/upcoming.m3u8";
- _logger.LogInformation(
- "URN {Urn}: Upcoming livestream '{Title}' - stream will be available at {ValidFrom}",
+ _logger.LogDebug(
+ "URN {Urn}: Skipping upcoming livestream '{Title}' - stream not yet available (starts at {ValidFrom})",
urn,
chapter.Title,
chapter.ValidFrom);
+ continue;
}
- // Build overview with event time for upcoming livestreams
- var overview = chapter.Description ?? chapter.Lead;
- if (isUpcomingLivestream && chapter.ValidFrom != null)
+ // Skip items without a valid stream URL
+ if (string.IsNullOrEmpty(streamUrl))
{
- var eventStart = chapter.ValidFrom.Value.ToString("dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture);
- overview = $"[Upcoming Event - Starts at {eventStart}]\n\n{overview}";
+ _logger.LogWarning(
+ "URN {Urn}: Skipping '{Title}' - no valid stream URL available",
+ urn,
+ chapter.Title);
+ noStreamCount++;
+ continue;
}
+ // Build overview
+ var overview = chapter.Description ?? chapter.Lead;
+
// Get image URL - prefer chapter image, fall back to show image if available
var imageUrl = chapter.ImageUrl;
if (string.IsNullOrEmpty(imageUrl) && mediaComposition.Show != null)
@@ -476,9 +474,10 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
_logger.LogWarning("URN {Urn}: No image URL available for '{Title}'", urn, chapter.Title);
}
- // Use ValidFrom for premiere date if this is an upcoming livestream, otherwise use Date
- var premiereDate = isUpcomingLivestream ? chapter.ValidFrom?.ToUniversalTime() : chapter.Date?.ToUniversalTime();
+ // Use ValidFrom for premiere date if this is a scheduled livestream, otherwise use Date
+ var premiereDate = chapter.Type == "SCHEDULED_LIVESTREAM" ? chapter.ValidFrom?.ToUniversalTime() : chapter.Date?.ToUniversalTime();
+ // Store unauthenticated URL as placeholder - SRFMediaProvider will provide authenticated version at playback
var item = new ChannelItemInfo
{
Id = itemId,
@@ -502,14 +501,14 @@ public class SRFPlayChannel : IChannel, IHasCacheKey
{
Id = itemId,
Name = chapter.Title,
- Path = streamUrl,
+ Path = streamUrl, // Unauthenticated URL - placeholder only
Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
Container = "m3u8",
- SupportsDirectStream = true,
- SupportsDirectPlay = true,
- SupportsTranscoding = true,
+ SupportsDirectPlay = false, // Disable direct play - requires auth from provider
+ SupportsDirectStream = false, // Disable direct stream - requires auth from provider
+ SupportsTranscoding = false, // Force use of IMediaSourceProvider
IsRemote = true,
- Type = MediaBrowser.Model.Dto.MediaSourceType.Default,
+ Type = MediaBrowser.Model.Dto.MediaSourceType.Placeholder, // Mark as placeholder
VideoType = VideoType.VideoFile
}
}
diff --git a/Jellyfin.Plugin.SRFPlay/Providers/SRFMediaProvider.cs b/Jellyfin.Plugin.SRFPlay/Providers/SRFMediaProvider.cs
index 8d45270..51b497b 100644
--- a/Jellyfin.Plugin.SRFPlay/Providers/SRFMediaProvider.cs
+++ b/Jellyfin.Plugin.SRFPlay/Providers/SRFMediaProvider.cs
@@ -119,37 +119,11 @@ public class SRFMediaProvider : IMediaSourceProvider
// Get stream URL based on quality preference
var streamUrl = _streamResolver.GetStreamUrl(chapter, config.QualityPreference);
- // Check if this is an upcoming livestream that hasn't started yet
- var isUpcomingLivestream = chapter.Type == "SCHEDULED_LIVESTREAM" && string.IsNullOrEmpty(streamUrl);
-
- if (string.IsNullOrEmpty(streamUrl) && !isUpcomingLivestream)
+ // For scheduled livestreams, always fetch fresh data to ensure stream URL is current
+ if (chapter.Type == "SCHEDULED_LIVESTREAM" && string.IsNullOrEmpty(streamUrl))
{
- _logger.LogWarning("Could not resolve stream URL for URN: {Urn}", urn);
- return Task.FromResult>(sources);
- }
+ _logger.LogDebug("URN {Urn}: Scheduled livestream has no stream URL, fetching fresh data", urn);
- // For upcoming livestreams, check if the event has started
- if (isUpcomingLivestream)
- {
- if (chapter.ValidFrom != null)
- {
- var eventStart = chapter.ValidFrom.Value.ToUniversalTime();
- var now = DateTime.UtcNow;
-
- if (eventStart > now)
- {
- _logger.LogInformation(
- "URN {Urn}: Livestream '{Title}' hasn't started yet (starts at {ValidFrom}). User should refresh when live.",
- urn,
- chapter.Title,
- chapter.ValidFrom);
-
- // Return empty sources - event not yet playable
- return Task.FromResult>(sources);
- }
- }
-
- // Event should be live now - re-fetch media composition without cache
using var freshApiClient = new SRFApiClient(_loggerFactory);
var freshMediaComposition = freshApiClient.GetMediaCompositionByUrnAsync(urn, cancellationToken).GetAwaiter().GetResult();
@@ -163,19 +137,15 @@ public class SRFMediaProvider : IMediaSourceProvider
// Update cache with fresh data
_metadataCache.SetMediaComposition(urn, freshMediaComposition);
chapter = freshChapter;
- _logger.LogInformation("URN {Urn}: Livestream is now live, got fresh stream URL", urn);
- }
- else
- {
- _logger.LogWarning("URN {Urn}: Livestream should be live but still no stream URL available", urn);
- return Task.FromResult>(sources);
+ _logger.LogInformation("URN {Urn}: Got fresh stream URL for scheduled livestream", urn);
}
}
- else
- {
- _logger.LogWarning("URN {Urn}: Failed to fetch fresh media composition for livestream", urn);
- return Task.FromResult>(sources);
- }
+ }
+
+ if (string.IsNullOrEmpty(streamUrl))
+ {
+ _logger.LogWarning("Could not resolve stream URL for URN: {Urn}", urn);
+ return Task.FromResult>(sources);
}
// Authenticate the stream URL (required for all SRF streams, especially livestreams)
@@ -188,13 +158,13 @@ public class SRFMediaProvider : IMediaSourceProvider
// Create media source
var mediaSource = new MediaSourceInfo
{
- Id = urn,
+ Id = item.Id.ToString(), // Use item GUID, not URN string (required for transcoding)
Name = chapter.Title,
Path = streamUrl,
Protocol = MediaProtocol.Http,
Container = "m3u8",
SupportsDirectStream = true,
- SupportsDirectPlay = true,
+ SupportsDirectPlay = false, // Disabled: auth tokens don't carry over to HLS segments in browser
SupportsTranscoding = true,
IsRemote = true,
Type = MediaSourceType.Default,
diff --git a/Jellyfin.Plugin.SRFPlay/Services/StreamUrlResolver.cs b/Jellyfin.Plugin.SRFPlay/Services/StreamUrlResolver.cs
index ffc9da3..a29e5a5 100644
--- a/Jellyfin.Plugin.SRFPlay/Services/StreamUrlResolver.cs
+++ b/Jellyfin.Plugin.SRFPlay/Services/StreamUrlResolver.cs
@@ -144,23 +144,6 @@ public class StreamUrlResolver : IDisposable
/// True if playable content is available.
public bool HasPlayableContent(Chapter chapter)
{
- // For scheduled livestreams that haven't started yet, resources won't exist
- // but we still want to show them. The stream will become available when the event starts.
- if (chapter?.Type == "SCHEDULED_LIVESTREAM")
- {
- var now = DateTime.UtcNow;
- var hasStarted = chapter.ValidFrom == null || chapter.ValidFrom.Value.ToUniversalTime() <= now;
-
- if (!hasStarted)
- {
- _logger.LogInformation(
- "Scheduled livestream '{Title}' hasn't started yet (starts at {ValidFrom}), will be playable when live",
- chapter.Title,
- chapter.ValidFrom);
- return true; // Show it, stream will be available when event starts
- }
- }
-
if (chapter?.ResourceList == null || chapter.ResourceList.Count == 0)
{
return false;