Files
stream-cinema/CinemaJellyfin/CinemaRootFolder.cs
2025-04-03 20:51:17 +00:00

230 lines
7.9 KiB
C#

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using CinemaLib.API;
using Jellyfin.Database.Implementations.Entities;
namespace Jellyfin.Plugin.Cinema;
/// <summary>
/// Cinema root folder that is displayed identically to user-defined libraries.
/// </summary>
public abstract class CinemaRootFolder : CinemaFilterFolder, ICollectionFolder
{
private bool _hidden;
/// <summary>
/// Gets the item type for <see cref="ICollectionFolder"/>. Shall be kept in
/// sync with value for <see cref="CinemaFilterFolder.ClientType"/>
/// </summary>
public abstract CollectionType? CollectionType { get; }
public override bool IsHidden => _hidden;
public override bool IsVisible(User user, bool skipAllowedTagsCheck = false) => !_hidden;
internal bool Hide
{
get => _hidden;
set => _hidden = value;
}
/*
public Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken)
{
if (type == ImageType.Primary)
{
return Task.FromResult(new DynamicImageResponse { Path = "https://streamcinema.cz/assets/logo-iqdz28nk.png", Protocol = MediaProtocol.Http, HasImage = true });
}
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}
public IEnumerable<ImageType> GetSupportedChannelImages()
{
return new List<ImageType> { ImageType.Primary };
}
#region IHasCacheKey Members
public string GetCacheKey(string userId)
{
DateTimeOffset dto = LiveTvService.Instance.RecordingModificationTime;
return $"{dto.ToUnixTimeSeconds()}-{_cacheKeyBase}";
}
#endregion
#region ISupportsLatestMedia Members
public async Task<IEnumerable<ChannelItemInfo>> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken)
{
var result = await GetChannelItems(new InternalChannelItemQuery(), _ => true, cancellationToken).ConfigureAwait(false);
return result.Items.OrderByDescending(i => i.DateCreated ?? DateTime.MinValue);
}
#endregion
#region IHasFolderAttributes Members
#pragma warning disable CA1819
public string[] Attributes => ["Recordings"];
#pragma warning restore CA1819
#endregion
private static string GetResourceUrl(string fileName)
{
return "__plugin/" + fileName;
}
private void CleanCache(bool cleanAll = false)
{
if (!string.IsNullOrEmpty(_recordingCacheDirectory) && Directory.Exists(_recordingCacheDirectory))
{
string[] cachedJson = Directory.GetFiles(_recordingCacheDirectory, "*.json");
_logger.LogInformation("Cleaning JSON cache {CacheDirectory} {FileCount}", _recordingCacheDirectory, cachedJson.Length);
foreach (string fileName in cachedJson)
{
if (cleanAll || _fileSystem.GetLastWriteTimeUtc(fileName).Add(TimeSpan.FromHours(3)) <= DateTimeOffset.UtcNow)
{
_fileSystem.DeleteFile(fileName);
}
}
}
}
public async Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, Func<MyRecordingInfo, bool> filter, CancellationToken cancellationToken)
{
await GetRecordingsAsync("GetChannelItems", cancellationToken);
List<ChannelItemInfo> pluginItems = new List<ChannelItemInfo>();
pluginItems.AddRange(_allRecordings.Where(filter).Select(ConvertToChannelItem));
var result = new ChannelItemResult() { Items = pluginItems };
return result;
}
private ChannelItemInfo ConvertToChannelItem(MyRecordingInfo item)
{
var path = string.IsNullOrEmpty(item.Path) ? item.Url : item.Path;
var channelItem = new ChannelItemInfo
{
Name = string.IsNullOrEmpty(item.EpisodeTitle) ? item.Name : item.EpisodeTitle,
SeriesName = !string.IsNullOrEmpty(item.EpisodeTitle) || item.IsSeries ? item.Name : null,
StartDate = item.StartDate,
EndDate = item.EndDate,
OfficialRating = item.OfficialRating,
CommunityRating = item.CommunityRating,
ContentType = item.IsMovie ? ChannelMediaContentType.Movie : ChannelMediaContentType.Episode,
Genres = item.Genres,
ImageUrl = item.ImageUrl,
Id = item.Id,
ParentIndexNumber = item.SeasonNumber,
IndexNumber = item.EpisodeNumber,
MediaType = item.ChannelType == ChannelType.TV ? ChannelMediaType.Video : ChannelMediaType.Audio,
MediaSources = new List<MediaSourceInfo>
{
new MediaSourceInfo
{
Path = path,
Container = item.Status == RecordingStatus.InProgress ? "ts" : null,
Protocol = path.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? MediaProtocol.Http : MediaProtocol.File,
BufferMs = 1000,
AnalyzeDurationMs = 0,
IsInfiniteStream = item.Status == RecordingStatus.InProgress,
TranscodingContainer = "ts",
RunTimeTicks = item.Status == RecordingStatus.InProgress ? null : (item.EndDate - item.StartDate).Ticks,
}
},
PremiereDate = item.OriginalAirDate,
ProductionYear = item.ProductionYear,
Type = ChannelItemType.Media,
DateModified = item.Status == RecordingStatus.InProgress ? DateTime.Now : Plugin.Instance.Configuration.RecordingModificationTime,
Overview = item.Overview,
IsLiveStream = item.Status != RecordingStatus.InProgress ? false : Plugin.Instance.Configuration.EnableInProgress,
Etag = item.Status.ToString()
};
return channelItem;
}
private async Task<bool> GetRecordingsAsync(string name, CancellationToken cancellationToken)
{
var service = GetService();
if (service is null || !service.IsActive)
{
return false;
}
if (_useCachedRecordings == false || service.FlagRecordingChange)
{
if (_pollInterval == -1)
{
var interval = TimeSpan.FromSeconds(Plugin.Instance.Configuration.PollInterval);
_updateTimer = new Timer(OnUpdateTimerCallbackAsync, null, TimeSpan.FromMinutes(2), interval);
if (_updateTimer != null)
{
_pollInterval = Plugin.Instance.Configuration.PollInterval;
}
}
if (await _semaphore.WaitAsync(30000, cancellationToken))
{
try
{
_logger.LogDebug("{0} Reload cache", name);
_allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
int maxId = _allRecordings.Max(r => int.Parse(r.Id, CultureInfo.InvariantCulture));
int inProcessCount = _allRecordings.Count(r => r.Status == RecordingStatus.InProgress);
string keyBase = $"{maxId}-{inProcessCount}-{_allRecordings.Count()}";
if (keyBase != _cacheKeyBase && !service.FlagRecordingChange)
{
_logger.LogDebug("External recording list change {0}", keyBase);
CleanCache(true);
}
_cacheKeyBase = keyBase;
_lastUpdate = DateTimeOffset.UtcNow;
service.FlagRecordingChange = false;
_useCachedRecordings = true;
}
catch (Exception)
{
}
_semaphore.Release();
}
}
return _useCachedRecordings;
}
private async void OnUpdateTimerCallbackAsync(object state)
{
LiveTvService service = LiveTvService.Instance;
if (service is not null && service.IsActive)
{
var backendUpdate = await service.GetLastUpdate(_cancellationToken.Token).ConfigureAwait(false);
if (backendUpdate > _lastUpdate)
{
_logger.LogDebug("Recordings reset {0}", backendUpdate);
_useCachedRecordings = false;
await GetRecordingsAsync("OnUpdateTimerCallbackAsync", _cancellationToken.Token);
}
}
}*/
}