Skip to content

Commit

Permalink
Group catchup items per day for easier navigation
Browse files Browse the repository at this point in the history
Also fixes a bug where the catchup items created from EPG entries do not have a GUID as ID.
  • Loading branch information
Kevinjil committed Jan 4, 2025
1 parent 3354992 commit 82781c7
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 20 deletions.
74 changes: 56 additions & 18 deletions Jellyfin.Xtream/CatchupChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public CatchupChannel(ILogger<CatchupChannel> logger)
public string? Description => "Rewatch IPTV streamed from the Xtream-compatible server.";

/// <inheritdoc />
public string DataVersion => Plugin.Instance.DataVersion;
public string DataVersion => Plugin.Instance.DataVersion + DateTime.Today.ToShortDateString();

/// <inheritdoc />
public string HomePageUrl => string.Empty;
Expand Down Expand Up @@ -109,8 +109,14 @@ public async Task<ChannelItemResult> 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)
{
Expand Down Expand Up @@ -150,8 +156,48 @@ private async Task<ChannelItemResult> GetChannels(CancellationToken cancellation
return result;
}

private async Task<ChannelItemResult> GetStreams(int categoryId, int channelId, CancellationToken cancellationToken)
private async Task<ChannelItemResult> 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<ChannelItemInfo> items = new List<ChannelItemInfo>();
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<string>(parsedName.Tags),
Type = ChannelItemType.Folder,
});
}

ChannelItemResult result = new ChannelItemResult()
{
Items = items,
TotalRecordCount = items.Count
};
return result;
}
}

private async Task<ChannelItemResult> 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())
{
Expand All @@ -169,18 +215,15 @@ 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<ChannelItemInfo>()
{
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<MediaSourceInfo>()
{
Expand All @@ -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<MediaSourceInfo> sources = new List<MediaSourceInfo>()
{
plugin.StreamService.GetMediaSourceInfo(StreamType.CatchUp, channelId, start: epg.StartLocalTime, durationMinutes: durationMinutes)
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions Jellyfin.Xtream/Service/StreamService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ public class StreamService
public const int CatchupPrefix = 0x5d774c3b;

/// <summary>
/// The id prefix for fallback EPG items.
/// The id prefix for catchup stream items.
/// </summary>
public const int FallbackPrefix = 0x5d774c3c;
public const int CatchupStreamPrefix = 0x5d774c3c;

/// <summary>
/// The id prefix for media source items.
Expand Down

0 comments on commit 82781c7

Please sign in to comment.