All checks were successful
continuous-integration/drone/push Build is passing
222 lines
7.6 KiB
C#
222 lines
7.6 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;
|
|
|
|
namespace Jellyfin.Plugin.Cinema;
|
|
|
|
/// <summary>
|
|
/// Cinema root folder that is displayed identically to user-defined libraries.
|
|
/// </summary>
|
|
public abstract class CinemaRootFolder : CinemaFilterFolder, ICollectionFolder
|
|
{
|
|
|
|
/// <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 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);
|
|
}
|
|
}
|
|
}*/
|
|
}
|