From 82781c7ad639b285dc82c699969b7665dcbe77f8 Mon Sep 17 00:00:00 2001 From: Kevin Jilissen Date: Sat, 4 Jan 2025 14:09:25 +0100 Subject: [PATCH] Group catchup items per day for easier navigation Also fixes a bug where the catchup items created from EPG entries do not have a GUID as ID. --- Jellyfin.Xtream/CatchupChannel.cs | 74 ++++++++++++++++++------ Jellyfin.Xtream/Service/StreamService.cs | 4 +- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/Jellyfin.Xtream/CatchupChannel.cs b/Jellyfin.Xtream/CatchupChannel.cs index 2a792d9..39faff9 100644 --- a/Jellyfin.Xtream/CatchupChannel.cs +++ b/Jellyfin.Xtream/CatchupChannel.cs @@ -54,7 +54,7 @@ public CatchupChannel(ILogger logger) public string? Description => "Rewatch IPTV streamed from the Xtream-compatible server."; /// - public string DataVersion => Plugin.Instance.DataVersion; + public string DataVersion => Plugin.Instance.DataVersion + DateTime.Today.ToShortDateString(); /// public string HomePageUrl => string.Empty; @@ -109,8 +109,14 @@ public async Task GetChannelItems(InternalChannelItemQuery qu } Guid guid = Guid.Parse(query.FolderId); - StreamService.FromGuid(guid, out int prefix, out int categoryId, out int channelId, out int _); - return await GetStreams(categoryId, channelId, cancellationToken).ConfigureAwait(false); + StreamService.FromGuid(guid, out int prefix, out int categoryId, out int channelId, out int date); + + if (date == 0) + { + return await GetDays(categoryId, channelId, cancellationToken).ConfigureAwait(false); + } + + return await GetStreams(categoryId, channelId, date, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -150,8 +156,48 @@ private async Task GetChannels(CancellationToken cancellation return result; } - private async Task GetStreams(int categoryId, int channelId, CancellationToken cancellationToken) + private async Task GetDays(int categoryId, int channelId, CancellationToken cancellationToken) + { + Plugin plugin = Plugin.Instance; + using (XtreamClient client = new XtreamClient()) + { + StreamInfo? channel = ( + await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false) + ).FirstOrDefault(s => s.StreamId == channelId); + if (channel == null) + { + throw new ArgumentException($"Channel with id {channelId} not found in category {categoryId}"); + } + + ParsedName parsedName = StreamService.ParseName(channel.Name); + List items = new List(); + for (int i = 0; i <= channel.TvArchiveDuration; i++) + { + DateTime channelDay = DateTime.Today.AddDays(-i); + int day = (int)(channelDay - DateTime.UnixEpoch).TotalDays; + items.Add(new ChannelItemInfo() + { + Id = StreamService.ToGuid(StreamService.CatchupPrefix, channel.CategoryId, channel.StreamId, day).ToString(), + ImageUrl = channel.StreamIcon, + Name = channelDay.ToLocalTime().ToString("ddd dd'-'MM'-'yyyy", CultureInfo.InvariantCulture), + Tags = new List(parsedName.Tags), + Type = ChannelItemType.Folder, + }); + } + + ChannelItemResult result = new ChannelItemResult() + { + Items = items, + TotalRecordCount = items.Count + }; + return result; + } + } + + private async Task GetStreams(int categoryId, int channelId, int day, CancellationToken cancellationToken) { + DateTime start = DateTime.UnixEpoch.AddDays(day); + DateTime end = start.AddDays(1); Plugin plugin = Plugin.Instance; using (XtreamClient client = new XtreamClient()) { @@ -169,9 +215,7 @@ await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellatio // Create fallback single-stream catch-up if no EPG is available. if (epgs.Listings.Count == 0) { - DateTime now = DateTime.UtcNow; - DateTime start = now.AddDays(-channel.TvArchiveDuration); - int duration = channel.TvArchiveDuration * 24 * 60; + int duration = 24 * 60; return new ChannelItemResult() { Items = new List() @@ -179,8 +223,7 @@ await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellatio new ChannelItemInfo() { ContentType = ChannelMediaContentType.TvExtra, - FolderType = ChannelFolderType.Container, - Id = StreamService.ToGuid(StreamService.FallbackPrefix, channelId, 0, 0).ToString(), + Id = StreamService.ToGuid(StreamService.CatchupStreamPrefix, channelId, 0, day).ToString(), IsLiveStream = false, MediaSources = new List() { @@ -191,19 +234,15 @@ await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellatio Type = ChannelItemType.Media, } }, - TotalRecordCount = items.Count + TotalRecordCount = 1 }; } - // Include all EPGs that start during the maximum cache interval of Jellyfin for channels. - DateTime startBefore = DateTime.UtcNow.AddHours(3); - DateTime startAfter = DateTime.UtcNow.AddDays(-channel.TvArchiveDuration); - foreach (EpgInfo epg in epgs.Listings.Where(epg => epg.Start < startBefore && epg.Start >= startAfter)) + foreach (EpgInfo epg in epgs.Listings.Where(epg => epg.Start <= end && epg.End >= start)) { - string id = epg.Id.ToString(System.Globalization.CultureInfo.InvariantCulture); ParsedName parsedName = StreamService.ParseName(epg.Title); int durationMinutes = (int)Math.Ceiling((epg.End - epg.Start).TotalMinutes); - string dateTitle = epg.Start.ToLocalTime().ToString("ddd HH:mm", CultureInfo.InvariantCulture); + string dateTitle = epg.Start.ToLocalTime().ToString("HH:mm", CultureInfo.InvariantCulture); List sources = new List() { plugin.StreamService.GetMediaSourceInfo(StreamType.CatchUp, channelId, start: epg.StartLocalTime, durationMinutes: durationMinutes) @@ -213,8 +252,7 @@ await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellatio { ContentType = ChannelMediaContentType.TvExtra, DateCreated = epg.Start, - FolderType = ChannelFolderType.Container, - Id = id, + Id = StreamService.ToGuid(StreamService.CatchupStreamPrefix, channel.StreamId, epg.Id, day).ToString(), IsLiveStream = false, MediaSources = sources, MediaType = ChannelMediaType.Video, diff --git a/Jellyfin.Xtream/Service/StreamService.cs b/Jellyfin.Xtream/Service/StreamService.cs index 72faa28..95f4cc5 100644 --- a/Jellyfin.Xtream/Service/StreamService.cs +++ b/Jellyfin.Xtream/Service/StreamService.cs @@ -73,9 +73,9 @@ public class StreamService public const int CatchupPrefix = 0x5d774c3b; /// - /// The id prefix for fallback EPG items. + /// The id prefix for catchup stream items. /// - public const int FallbackPrefix = 0x5d774c3c; + public const int CatchupStreamPrefix = 0x5d774c3c; /// /// The id prefix for media source items.