diff --git a/Jellyfin.Xtream/Api/XtreamController.cs b/Jellyfin.Xtream/Api/XtreamController.cs index 2a0b280..031bb19 100644 --- a/Jellyfin.Xtream/Api/XtreamController.cs +++ b/Jellyfin.Xtream/Api/XtreamController.cs @@ -35,26 +35,15 @@ namespace Jellyfin.Xtream.Api; [Produces(MediaTypeNames.Application.Json)] public class XtreamController : ControllerBase { - private readonly ILogger logger; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - public XtreamController(ILogger logger) - { - this.logger = logger; - } - private static CategoryResponse CreateCategoryResponse(Category category) => - new CategoryResponse() + new() { Id = category.CategoryId, Name = category.CategoryName, }; private static ItemResponse CreateItemResponse(StreamInfo stream) => - new ItemResponse() + new() { Id = stream.StreamId, Name = stream.Name, @@ -63,7 +52,7 @@ public class XtreamController : ControllerBase }; private static ItemResponse CreateItemResponse(Series series) => - new ItemResponse() + new() { Id = series.SeriesId, Name = series.Name, @@ -72,7 +61,7 @@ public class XtreamController : ControllerBase }; private static ChannelResponse CreateChannelResponse(StreamInfo stream) => - new ChannelResponse() + new() { Id = stream.StreamId, LogoUrl = stream.StreamIcon, @@ -90,11 +79,9 @@ public class XtreamController : ControllerBase public async Task>> GetLiveCategories(CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - using (XtreamClient client = new XtreamClient()) - { - List categories = await client.GetLiveCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false); - return Ok(categories.Select((Category c) => CreateCategoryResponse(c))); - } + using XtreamClient client = new XtreamClient(); + List categories = await client.GetLiveCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false); + return Ok(categories.Select(CreateCategoryResponse)); } /// @@ -108,14 +95,12 @@ public class XtreamController : ControllerBase public async Task>> GetLiveStreams(int categoryId, CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - using (XtreamClient client = new XtreamClient()) - { - List streams = await client.GetLiveStreamsByCategoryAsync( - plugin.Creds, - categoryId, - cancellationToken).ConfigureAwait(false); - return Ok(streams.Select((StreamInfo s) => CreateItemResponse(s))); - } + using XtreamClient client = new XtreamClient(); + List streams = await client.GetLiveStreamsByCategoryAsync( + plugin.Creds, + categoryId, + cancellationToken).ConfigureAwait(false); + return Ok(streams.Select(CreateItemResponse)); } /// @@ -128,11 +113,9 @@ public class XtreamController : ControllerBase public async Task>> GetVodCategories(CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - using (XtreamClient client = new XtreamClient()) - { - List categories = await client.GetVodCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false); - return Ok(categories.Select((Category c) => CreateCategoryResponse(c))); - } + using XtreamClient client = new XtreamClient(); + List categories = await client.GetVodCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false); + return Ok(categories.Select(CreateCategoryResponse)); } /// @@ -146,14 +129,12 @@ public class XtreamController : ControllerBase public async Task>> GetVodStreams(int categoryId, CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - using (XtreamClient client = new XtreamClient()) - { - List streams = await client.GetVodStreamsByCategoryAsync( - plugin.Creds, - categoryId, - cancellationToken).ConfigureAwait(false); - return Ok(streams.Select((StreamInfo s) => CreateItemResponse(s))); - } + using XtreamClient client = new XtreamClient(); + List streams = await client.GetVodStreamsByCategoryAsync( + plugin.Creds, + categoryId, + cancellationToken).ConfigureAwait(false); + return Ok(streams.Select(CreateItemResponse)); } /// @@ -166,11 +147,9 @@ public class XtreamController : ControllerBase public async Task>> GetSeriesCategories(CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - using (XtreamClient client = new XtreamClient()) - { - List categories = await client.GetSeriesCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false); - return Ok(categories.Select((Category c) => CreateCategoryResponse(c))); - } + using XtreamClient client = new XtreamClient(); + List categories = await client.GetSeriesCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false); + return Ok(categories.Select(CreateCategoryResponse)); } /// @@ -184,14 +163,12 @@ public class XtreamController : ControllerBase public async Task>> GetSeriesStreams(int categoryId, CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - using (XtreamClient client = new XtreamClient()) - { - List series = await client.GetSeriesByCategoryAsync( - plugin.Creds, - categoryId, - cancellationToken).ConfigureAwait(false); - return Ok(series.Select((Series s) => CreateItemResponse(s))); - } + using XtreamClient client = new XtreamClient(); + List series = await client.GetSeriesByCategoryAsync( + plugin.Creds, + categoryId, + cancellationToken).ConfigureAwait(false); + return Ok(series.Select(CreateItemResponse)); } /// diff --git a/Jellyfin.Xtream/CatchupChannel.cs b/Jellyfin.Xtream/CatchupChannel.cs index 2d082e2..0814052 100644 --- a/Jellyfin.Xtream/CatchupChannel.cs +++ b/Jellyfin.Xtream/CatchupChannel.cs @@ -34,18 +34,10 @@ namespace Jellyfin.Xtream; /// /// The Xtream Codes API channel. /// -public class CatchupChannel : IChannel +/// Instance of the interface. +public class CatchupChannel(ILogger logger) : IChannel { - private readonly ILogger logger; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - public CatchupChannel(ILogger logger) - { - this.logger = logger; - } + private readonly ILogger _logger = logger; /// public string? Name => "Xtream Catch-up"; @@ -67,15 +59,12 @@ public class CatchupChannel : IChannel { return new InternalChannelFeatures { - ContentTypes = new List - { + ContentTypes = [ ChannelMediaContentType.TvExtra, - }, - - MediaTypes = new List - { + ], + MediaTypes = [ ChannelMediaType.Video - }, + ], }; } @@ -90,13 +79,10 @@ public class CatchupChannel : IChannel } /// - public IEnumerable GetSupportedChannelImages() + public IEnumerable GetSupportedChannelImages() => new List { - return new List - { - // ImageType.Primary - }; - } + // ImageType.Primary + }; /// public async Task GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken) @@ -120,7 +106,7 @@ public class CatchupChannel : IChannel } catch (Exception ex) { - logger.LogError(ex, "Failed to get channel items"); + _logger.LogError(ex, "Failed to get channel items"); throw; } } @@ -128,7 +114,7 @@ public class CatchupChannel : IChannel private async Task GetChannels(CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - List items = new List(); + List items = []; foreach (StreamInfo channel in await plugin.StreamService.GetLiveStreamsWithOverrides(cancellationToken).ConfigureAwait(false)) { if (!channel.TvArchive) @@ -159,39 +145,34 @@ public class CatchupChannel : IChannel private async Task GetDays(int categoryId, int channelId, CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - using (XtreamClient client = new XtreamClient()) + using XtreamClient client = new XtreamClient(); + + List streams = await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false); + StreamInfo channel = streams.FirstOrDefault(s => s.StreamId == channelId) + ?? throw new ArgumentException($"Channel with id {channelId} not found in category {categoryId}"); + ParsedName parsedName = StreamService.ParseName(channel.Name); + + List items = []; + for (int i = 0; i <= channel.TvArchiveDuration; i++) { - StreamInfo? channel = ( - await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false) - ).FirstOrDefault(s => s.StreamId == channelId); - if (channel == null) + DateTime channelDay = DateTime.Today.AddDays(-i); + int day = (int)(channelDay - DateTime.UnixEpoch).TotalDays; + items.Add(new() { - 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; + 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() + { + Items = items, + TotalRecordCount = items.Count + }; + return result; } private async Task GetStreams(int categoryId, int channelId, int day, CancellationToken cancellationToken) @@ -199,78 +180,70 @@ public class CatchupChannel : IChannel DateTime start = DateTime.UnixEpoch.AddDays(day); DateTime end = start.AddDays(1); Plugin plugin = Plugin.Instance; - using (XtreamClient client = new XtreamClient()) + using XtreamClient client = new XtreamClient(); + + List streams = await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false); + StreamInfo channel = streams.FirstOrDefault(s => s.StreamId == channelId) + ?? throw new ArgumentException($"Channel with id {channelId} not found in category {categoryId}"); + EpgListings epgs = await client.GetEpgInfoAsync(plugin.Creds, channelId, cancellationToken).ConfigureAwait(false); + List items = []; + + // Create fallback single-stream catch-up if no EPG is available. + if (epgs.Listings.Count == 0) { - StreamInfo? channel = ( - await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false) - ).FirstOrDefault(s => s.StreamId == channelId); - if (channel == null) + int duration = 24 * 60; + return new() { - throw new ArgumentException($"Channel with id {channelId} not found in category {categoryId}"); - } - - EpgListings epgs = await client.GetEpgInfoAsync(plugin.Creds, channelId, cancellationToken).ConfigureAwait(false); - List items = new List(); - - // Create fallback single-stream catch-up if no EPG is available. - if (epgs.Listings.Count == 0) - { - int duration = 24 * 60; - return new ChannelItemResult() - { - Items = new List() + Items = new List() { - new ChannelItemInfo() + new() { ContentType = ChannelMediaContentType.TvExtra, Id = StreamService.ToGuid(StreamService.CatchupStreamPrefix, channelId, 0, day).ToString(), IsLiveStream = false, - MediaSources = new List() - { + MediaSources = [ plugin.StreamService.GetMediaSourceInfo(StreamType.CatchUp, channelId, start: start, durationMinutes: duration) - }, + ], MediaType = ChannelMediaType.Video, Name = $"No EPG available", Type = ChannelItemType.Media, } }, - TotalRecordCount = 1 - }; - } - - foreach (EpgInfo epg in epgs.Listings.Where(epg => epg.Start <= end && epg.End >= start)) - { - ParsedName parsedName = StreamService.ParseName(epg.Title); - int durationMinutes = (int)Math.Ceiling((epg.End - epg.Start).TotalMinutes); - 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) - }; - - items.Add(new ChannelItemInfo() - { - ContentType = ChannelMediaContentType.TvExtra, - DateCreated = epg.Start, - Id = StreamService.ToGuid(StreamService.CatchupStreamPrefix, channel.StreamId, epg.Id, day).ToString(), - IsLiveStream = false, - MediaSources = sources, - MediaType = ChannelMediaType.Video, - Name = $"{dateTitle} - {parsedName.Title}", - Overview = epg.Description, - PremiereDate = epg.Start, - Tags = new List(parsedName.Tags), - Type = ChannelItemType.Media, - }); - } - - ChannelItemResult result = new ChannelItemResult() - { - Items = items, - TotalRecordCount = items.Count + TotalRecordCount = 1 }; - return result; } + + foreach (EpgInfo epg in epgs.Listings.Where(epg => epg.Start <= end && epg.End >= start)) + { + ParsedName parsedName = StreamService.ParseName(epg.Title); + int durationMinutes = (int)Math.Ceiling((epg.End - epg.Start).TotalMinutes); + string dateTitle = epg.Start.ToLocalTime().ToString("HH:mm", CultureInfo.InvariantCulture); + List sources = [ + plugin.StreamService.GetMediaSourceInfo(StreamType.CatchUp, channelId, start: epg.StartLocalTime, durationMinutes: durationMinutes) + ]; + + items.Add(new() + { + ContentType = ChannelMediaContentType.TvExtra, + DateCreated = epg.Start, + Id = StreamService.ToGuid(StreamService.CatchupStreamPrefix, channel.StreamId, epg.Id, day).ToString(), + IsLiveStream = false, + MediaSources = sources, + MediaType = ChannelMediaType.Video, + Name = $"{dateTitle} - {parsedName.Title}", + Overview = epg.Description, + PremiereDate = epg.Start, + Tags = new List(parsedName.Tags), + Type = ChannelItemType.Media, + }); + } + + ChannelItemResult result = new() + { + Items = items, + TotalRecordCount = items.Count + }; + return result; } /// diff --git a/Jellyfin.Xtream/Client/ConnectionInfo.cs b/Jellyfin.Xtream/Client/ConnectionInfo.cs index d932cdb..4a70b51 100644 --- a/Jellyfin.Xtream/Client/ConnectionInfo.cs +++ b/Jellyfin.Xtream/Client/ConnectionInfo.cs @@ -18,35 +18,25 @@ namespace Jellyfin.Xtream.Client; /// /// A wrapper class for Xtream API client connection information. /// -public class ConnectionInfo +/// The base url including protocol and port number, without trailing slash. +/// The username for authentication. +/// The password for authentication. +public class ConnectionInfo(string baseUrl, string username, string password) { - /// - /// Initializes a new instance of the class. - /// - /// The base url including protocol and port number, without trailing slash. - /// The username for authentication. - /// The password for authentication. - public ConnectionInfo(string baseUrl, string username, string password) - { - BaseUrl = baseUrl; - UserName = username; - Password = password; - } - /// /// Gets or sets the base url including protocol and port number, without trailing slash. /// - public string BaseUrl { get; set; } + public string BaseUrl { get; set; } = baseUrl; /// /// Gets or sets the username for authentication. /// - public string UserName { get; set; } + public string UserName { get; set; } = username; /// /// Gets or sets the password for authentication. /// - public string Password { get; set; } + public string Password { get; set; } = password; /// public override string ToString() => $"{BaseUrl} {UserName}:{Password}"; diff --git a/Jellyfin.Xtream/Client/XtreamClient.cs b/Jellyfin.Xtream/Client/XtreamClient.cs index b635850..b24219c 100644 --- a/Jellyfin.Xtream/Client/XtreamClient.cs +++ b/Jellyfin.Xtream/Client/XtreamClient.cs @@ -27,10 +27,12 @@ namespace Jellyfin.Xtream.Client; /// /// The Xtream API client implementation. /// -public class XtreamClient : IDisposable +/// +/// Initializes a new instance of the class. +/// +/// The HTTP client used. +public class XtreamClient(HttpClient client) : IDisposable { - private readonly HttpClient _client; - /// /// Initializes a new instance of the class. /// @@ -38,19 +40,10 @@ public class XtreamClient : IDisposable { } - /// - /// Initializes a new instance of the class. - /// - /// The HTTP client used. - public XtreamClient(HttpClient client) - { - _client = client; - } - private async Task QueryApi(ConnectionInfo connectionInfo, string urlPath, CancellationToken cancellationToken) { Uri uri = new Uri(connectionInfo.BaseUrl + urlPath); - string jsonContent = await _client.GetStringAsync(uri, cancellationToken).ConfigureAwait(false); + string jsonContent = await client.GetStringAsync(uri, cancellationToken).ConfigureAwait(false); return JsonConvert.DeserializeObject(jsonContent)!; } @@ -114,7 +107,7 @@ public class XtreamClient : IDisposable /// Unused. protected virtual void Dispose(bool b) { - _client?.Dispose(); + client?.Dispose(); } /// diff --git a/Jellyfin.Xtream/Configuration/ChannelOverrides.cs b/Jellyfin.Xtream/Configuration/ChannelOverrides.cs index 19a0b8c..2e7e85d 100644 --- a/Jellyfin.Xtream/Configuration/ChannelOverrides.cs +++ b/Jellyfin.Xtream/Configuration/ChannelOverrides.cs @@ -20,13 +20,6 @@ namespace Jellyfin.Xtream.Configuration; /// public class ChannelOverrides { - /// - /// Initializes a new instance of the class. - /// - public ChannelOverrides() - { - } - /// /// Gets or sets the TV channel number. /// diff --git a/Jellyfin.Xtream/Configuration/PluginConfiguration.cs b/Jellyfin.Xtream/Configuration/PluginConfiguration.cs index 1af504b..c70caf4 100644 --- a/Jellyfin.Xtream/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Xtream/Configuration/PluginConfiguration.cs @@ -24,38 +24,20 @@ namespace Jellyfin.Xtream.Configuration; /// public class PluginConfiguration : BasePluginConfiguration { - /// - /// Initializes a new instance of the class. - /// - public PluginConfiguration() - { - // set default options here - BaseUrl = "https://example.com"; - Username = string.Empty; - Password = string.Empty; - IsCatchupVisible = false; - IsSeriesVisible = false; - IsVodVisible = false; - LiveTv = new SerializableDictionary>(); - Vod = new SerializableDictionary>(); - Series = new SerializableDictionary>(); - LiveTvOverrides = new SerializableDictionary(); - } - /// /// Gets or sets the base url including protocol and trailing slash. /// - public string BaseUrl { get; set; } + public string BaseUrl { get; set; } = "https://example.com"; /// /// Gets or sets the username. /// - public string Username { get; set; } + public string Username { get; set; } = string.Empty; /// /// Gets or sets the password. /// - public string Password { get; set; } + public string Password { get; set; } = string.Empty; /// /// Gets or sets a value indicating whether the Catch-up channel is visible. @@ -75,21 +57,21 @@ public class PluginConfiguration : BasePluginConfiguration /// /// Gets or sets the channels displayed in Live TV. /// - public SerializableDictionary> LiveTv { get; set; } + public SerializableDictionary> LiveTv { get; set; } = []; /// /// Gets or sets the streams displayed in VOD. /// - public SerializableDictionary> Vod { get; set; } + public SerializableDictionary> Vod { get; set; } = []; /// /// Gets or sets the streams displayed in Series. /// - public SerializableDictionary> Series { get; set; } + public SerializableDictionary> Series { get; set; } = []; /// /// Gets or sets the channel override configuration for Live TV. /// - public SerializableDictionary LiveTvOverrides { get; set; } + public SerializableDictionary LiveTvOverrides { get; set; } = []; } #pragma warning restore CA2227 diff --git a/Jellyfin.Xtream/Configuration/SerializableDictionary.cs b/Jellyfin.Xtream/Configuration/SerializableDictionary.cs index cdc84f7..d027b71 100644 --- a/Jellyfin.Xtream/Configuration/SerializableDictionary.cs +++ b/Jellyfin.Xtream/Configuration/SerializableDictionary.cs @@ -32,15 +32,15 @@ namespace Jellyfin.Xtream.Configuration; public sealed class SerializableDictionary : Dictionary, IXmlSerializable where TKey : notnull { - private const string DefaultItemTag = "Item"; + private const string ItemTag = "Item"; - private const string DefaultKeyTag = "Key"; + private const string KeyTag = "Key"; - private const string DefaultValueTag = "Value"; + private const string ValueTag = "Value"; - private static readonly XmlSerializer KeySerializer = new XmlSerializer(typeof(TKey)); + private static readonly XmlSerializer _keySerializer = new(typeof(TKey)); - private static readonly XmlSerializer ValueSerializer = new XmlSerializer(typeof(TValue)); + private static readonly XmlSerializer _valueSerializer = new(typeof(TValue)); /// Initializes a new instance of the /// class. @@ -49,12 +49,6 @@ where TKey : notnull { } - private string ItemTagName => DefaultItemTag; - - private string KeyTagName => DefaultKeyTag; - - private string ValueTagName => DefaultValueTag; - /// public XmlSchema? GetSchema() { @@ -91,7 +85,7 @@ where TKey : notnull { foreach (var keyValuePair in this) { - WriteItem(writer, keyValuePair); + SerializableDictionary.WriteItem(writer, keyValuePair); } } @@ -101,10 +95,10 @@ where TKey : notnull /// The XML representation of the object. private void ReadItem(XmlReader reader) { - reader.ReadStartElement(ItemTagName); + reader.ReadStartElement(ItemTag); try { - Add(ReadKey(reader), ReadValue(reader)); + Add(SerializableDictionary.ReadKey(reader), SerializableDictionary.ReadValue(reader)); } finally { @@ -117,17 +111,12 @@ where TKey : notnull /// /// The XML representation of the object. /// The dictionary item's key. - private TKey ReadKey(XmlReader reader) + private static TKey ReadKey(XmlReader reader) { - reader.ReadStartElement(KeyTagName); + reader.ReadStartElement(KeyTag); try { - TKey? deserialized = (TKey?)KeySerializer.Deserialize(reader); - if (deserialized == null) - { - throw new SerializationException("Key cannot be null"); - } - + TKey deserialized = (TKey?)_keySerializer.Deserialize(reader) ?? throw new SerializationException("Key cannot be null"); return deserialized; } finally @@ -141,17 +130,12 @@ where TKey : notnull /// /// The XML representation of the object. /// The dictionary item's value. - private TValue ReadValue(XmlReader reader) + private static TValue ReadValue(XmlReader reader) { - reader.ReadStartElement(ValueTagName); + reader.ReadStartElement(ValueTag); try { - TValue? deserialized = (TValue?)ValueSerializer.Deserialize(reader); - if (deserialized == null) - { - throw new SerializationException("Value cannot be null"); - } - + TValue deserialized = (TValue?)_valueSerializer.Deserialize(reader) ?? throw new SerializationException("Value cannot be null"); return deserialized; } finally @@ -165,13 +149,13 @@ where TKey : notnull /// /// The XML writer to serialize to. /// The key/value pair. - private void WriteItem(XmlWriter writer, KeyValuePair keyValuePair) + private static void WriteItem(XmlWriter writer, KeyValuePair keyValuePair) { - writer.WriteStartElement(ItemTagName); + writer.WriteStartElement(ItemTag); try { - WriteKey(writer, keyValuePair.Key); - WriteValue(writer, keyValuePair.Value); + SerializableDictionary.WriteKey(writer, keyValuePair.Key); + SerializableDictionary.WriteValue(writer, keyValuePair.Value); } finally { @@ -184,12 +168,12 @@ where TKey : notnull /// /// The XML writer to serialize to. /// The dictionary item's key. - private void WriteKey(XmlWriter writer, TKey key) + private static void WriteKey(XmlWriter writer, TKey key) { - writer.WriteStartElement(KeyTagName); + writer.WriteStartElement(KeyTag); try { - KeySerializer.Serialize(writer, key); + _keySerializer.Serialize(writer, key); } finally { @@ -202,12 +186,12 @@ where TKey : notnull /// /// The XML writer to serialize to. /// The dictionary item's value. - private void WriteValue(XmlWriter writer, TValue value) + private static void WriteValue(XmlWriter writer, TValue value) { - writer.WriteStartElement(ValueTagName); + writer.WriteStartElement(ValueTag); try { - ValueSerializer.Serialize(writer, value); + _valueSerializer.Serialize(writer, value); } finally { diff --git a/Jellyfin.Xtream/LiveTvService.cs b/Jellyfin.Xtream/LiveTvService.cs index 813a98d..fe72b53 100644 --- a/Jellyfin.Xtream/LiveTvService.cs +++ b/Jellyfin.Xtream/LiveTvService.cs @@ -35,28 +35,15 @@ namespace Jellyfin.Xtream; /// /// Class LiveTvService. /// -public class LiveTvService : ILiveTvService, ISupportsDirectStreamProvider +/// +/// Initializes a new instance of the class. +/// +/// Instance of the interface. +/// Instance of the interface. +/// Instance of the interface. +/// Instance of the interface. +public class LiveTvService(IServerApplicationHost appHost, IHttpClientFactory httpClientFactory, ILogger logger, IMemoryCache memoryCache) : ILiveTvService, ISupportsDirectStreamProvider { - private readonly IServerApplicationHost appHost; - private readonly IHttpClientFactory httpClientFactory; - private readonly ILogger logger; - private readonly IMemoryCache memoryCache; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - public LiveTvService(IServerApplicationHost appHost, IHttpClientFactory httpClientFactory, ILogger logger, IMemoryCache memoryCache) - { - this.appHost = appHost; - this.httpClientFactory = httpClientFactory; - this.logger = logger; - this.memoryCache = memoryCache; - } - /// public string Name => "Xtream Live"; @@ -67,7 +54,7 @@ public class LiveTvService : ILiveTvService, ISupportsDirectStreamProvider public async Task> GetChannelsAsync(CancellationToken cancellationToken) { Plugin plugin = Plugin.Instance; - List items = new List(); + List items = []; foreach (StreamInfo channel in await plugin.StreamService.GetLiveStreamsWithOverrides(cancellationToken).ConfigureAwait(false)) { ParsedName parsed = StreamService.ParseName(channel.Name); @@ -136,7 +123,7 @@ public class LiveTvService : ILiveTvService, ISupportsDirectStreamProvider public async Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken) { MediaSourceInfo source = await GetChannelStream(channelId, string.Empty, cancellationToken).ConfigureAwait(false); - return new List() { source }; + return [source]; } /// @@ -190,7 +177,7 @@ public class LiveTvService : ILiveTvService, ISupportsDirectStreamProvider EpgListings epgs = await client.GetEpgInfoAsync(plugin.Creds, streamId, cancellationToken).ConfigureAwait(false); foreach (EpgInfo epg in epgs.Listings) { - items.Add(new ProgramInfo() + items.Add(new() { Id = StreamService.ToGuid(StreamService.EpgPrefix, streamId, epg.Id, 0).ToString(), ChannelId = channelId, diff --git a/Jellyfin.Xtream/Plugin.cs b/Jellyfin.Xtream/Plugin.cs index 99937d7..0b0e74f 100644 --- a/Jellyfin.Xtream/Plugin.cs +++ b/Jellyfin.Xtream/Plugin.cs @@ -34,24 +34,20 @@ namespace Jellyfin.Xtream; /// public class Plugin : BasePlugin, IHasWebPages { - private static Plugin? instance; - - private readonly ILogger _logger; + private static Plugin? _instance; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the interface. - public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, ILogger logger, ITaskManager taskManager) + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, ITaskManager taskManager) : base(applicationPaths, xmlSerializer) { - _logger = logger; - instance = this; - StreamService = new StreamService(logger, this); - TaskService = new TaskService(logger, this, taskManager); + _instance = this; + StreamService = new(); + TaskService = new(taskManager); } /// @@ -63,10 +59,7 @@ public class Plugin : BasePlugin, IHasWebPages /// /// Gets the Xtream connection info with credentials. /// - public ConnectionInfo Creds - { - get => new ConnectionInfo(Configuration.BaseUrl, Configuration.Username, Configuration.Password); - } + public ConnectionInfo Creds => new(Configuration.BaseUrl, Configuration.Username, Configuration.Password); /// /// Gets the data version used to trigger a cache invalidation on plugin update or config change. @@ -76,18 +69,7 @@ public class Plugin : BasePlugin, IHasWebPages /// /// Gets the current plugin instance. /// - public static Plugin Instance - { - get - { - if (instance == null) - { - throw new InvalidOperationException("Plugin instance not available"); - } - - return instance; - } - } + public static Plugin Instance => _instance ?? throw new InvalidOperationException("Plugin instance not available"); /// /// Gets the stream service instance. @@ -99,7 +81,7 @@ public class Plugin : BasePlugin, IHasWebPages /// public TaskService TaskService { get; init; } - private static PluginPageInfo CreateStatic(string name) => new PluginPageInfo + private static PluginPageInfo CreateStatic(string name) => new() { Name = name, EmbeddedResourcePath = string.Format( diff --git a/Jellyfin.Xtream/SeriesChannel.cs b/Jellyfin.Xtream/SeriesChannel.cs index ae5867c..a0f1e8e 100644 --- a/Jellyfin.Xtream/SeriesChannel.cs +++ b/Jellyfin.Xtream/SeriesChannel.cs @@ -34,19 +34,9 @@ namespace Jellyfin.Xtream; /// /// The Xtream Codes API channel. /// -public class SeriesChannel : IChannel +/// Instance of the interface. +public class SeriesChannel(ILogger logger) : IChannel { - private readonly ILogger logger; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - public SeriesChannel(ILogger logger) - { - this.logger = logger; - } - /// public string? Name => "Xtream Series"; @@ -67,15 +57,13 @@ public class SeriesChannel : IChannel { return new InternalChannelFeatures { - ContentTypes = new List - { + ContentTypes = [ ChannelMediaContentType.Episode, - }, + ], - MediaTypes = new List - { + MediaTypes = [ ChannelMediaType.Video - }, + ], }; } @@ -155,12 +143,12 @@ public class SeriesChannel : IChannel }; } - private List GetGenres(string genreString) + private static List GetGenres(string genreString) { - return new List(genreString.Split(',').Select(genre => genre.Trim())); + return new(genreString.Split(',').Select(genre => genre.Trim())); } - private List GetPeople(string cast) + private static List GetPeople(string cast) { return cast.Split(',').Select(name => new PersonInfo() { @@ -170,12 +158,12 @@ public class SeriesChannel : IChannel private ChannelItemInfo CreateChannelItemInfo(int seriesId, SeriesStreamInfo series, int seasonId) { - Jellyfin.Xtream.Client.Models.SeriesInfo serie = series.Info; + Client.Models.SeriesInfo serie = series.Info; string name = $"Season {seasonId}"; string cover = series.Info.Cover; string? overview = null; DateTime? created = null; - List tags = new List(); + List tags = []; Season? season = series.Seasons.FirstOrDefault(s => s.SeasonId == seasonId); if (season != null) @@ -191,7 +179,7 @@ public class SeriesChannel : IChannel } } - return new ChannelItemInfo() + return new() { DateCreated = created, // FolderType = ChannelFolderType.Season, @@ -208,12 +196,11 @@ public class SeriesChannel : IChannel private ChannelItemInfo CreateChannelItemInfo(SeriesStreamInfo series, Season? season, Episode episode) { - Jellyfin.Xtream.Client.Models.SeriesInfo serie = series.Info; + Client.Models.SeriesInfo serie = series.Info; ParsedName parsedName = StreamService.ParseName(episode.Title); - List sources = new List() - { + List sources = [ Plugin.Instance.StreamService.GetMediaSourceInfo(StreamType.Series, episode.EpisodeId, episode.ContainerExtension) - }; + ]; string? cover = episode.Info?.MovieImage; if (string.IsNullOrEmpty(cover) && season != null) @@ -226,7 +213,7 @@ public class SeriesChannel : IChannel cover = serie.Cover; } - return new ChannelItemInfo() + return new() { ContentType = ChannelMediaContentType.Episode, DateCreated = DateTimeOffset.FromUnixTimeSeconds(episode.Added).DateTime, @@ -239,17 +226,17 @@ public class SeriesChannel : IChannel Name = parsedName.Title, Overview = episode.Info?.Plot, People = GetPeople(serie.Cast), - Tags = new List(parsedName.Tags), + Tags = new(parsedName.Tags), Type = ChannelItemType.Media, }; } private async Task GetCategories(CancellationToken cancellationToken) { - List items = new List( - (await Plugin.Instance.StreamService.GetSeriesCategories(cancellationToken).ConfigureAwait(false)) - .Select((Category category) => StreamService.CreateChannelItemInfo(StreamService.SeriesCategoryPrefix, category))); - return new ChannelItemResult() + IEnumerable categories = await Plugin.Instance.StreamService.GetSeriesCategories(cancellationToken).ConfigureAwait(false); + List items = new( + categories.Select((Category category) => StreamService.CreateChannelItemInfo(StreamService.SeriesCategoryPrefix, category))); + return new() { Items = items, TotalRecordCount = items.Count @@ -258,10 +245,9 @@ public class SeriesChannel : IChannel private async Task GetSeries(int categoryId, CancellationToken cancellationToken) { - List items = new List( - (await Plugin.Instance.StreamService.GetSeries(categoryId, cancellationToken).ConfigureAwait(false)) - .Select((Series series) => CreateChannelItemInfo(series))); - return new ChannelItemResult() + IEnumerable series = await Plugin.Instance.StreamService.GetSeries(categoryId, cancellationToken).ConfigureAwait(false); + List items = new(series.Select(CreateChannelItemInfo)); + return new() { Items = items, TotalRecordCount = items.Count @@ -270,10 +256,10 @@ public class SeriesChannel : IChannel private async Task GetSeasons(int seriesId, CancellationToken cancellationToken) { - List items = new List( - (await Plugin.Instance.StreamService.GetSeasons(seriesId, cancellationToken).ConfigureAwait(false)) - .Select((Tuple tuple) => CreateChannelItemInfo(seriesId, tuple.Item1, tuple.Item2))); - return new ChannelItemResult() + IEnumerable> seasons = await Plugin.Instance.StreamService.GetSeasons(seriesId, cancellationToken).ConfigureAwait(false); + List items = new( + seasons.Select((Tuple tuple) => CreateChannelItemInfo(seriesId, tuple.Item1, tuple.Item2))); + return new() { Items = items, TotalRecordCount = items.Count @@ -282,10 +268,10 @@ public class SeriesChannel : IChannel private async Task GetEpisodes(int seriesId, int seasonId, CancellationToken cancellationToken) { + IEnumerable> episodes = await Plugin.Instance.StreamService.GetEpisodes(seriesId, seasonId, cancellationToken).ConfigureAwait(false); List items = new List( - (await Plugin.Instance.StreamService.GetEpisodes(seriesId, seasonId, cancellationToken).ConfigureAwait(false)) - .Select((Tuple tuple) => CreateChannelItemInfo(tuple.Item1, tuple.Item2, tuple.Item3))); - return new ChannelItemResult() + episodes.Select((Tuple tuple) => CreateChannelItemInfo(tuple.Item1, tuple.Item2, tuple.Item3))); + return new() { Items = items, TotalRecordCount = items.Count diff --git a/Jellyfin.Xtream/Service/ParsedName.cs b/Jellyfin.Xtream/Service/ParsedName.cs index 2d2ced2..678a0a7 100644 --- a/Jellyfin.Xtream/Service/ParsedName.cs +++ b/Jellyfin.Xtream/Service/ParsedName.cs @@ -20,26 +20,17 @@ namespace Jellyfin.Xtream.Service; /// /// A struct which holds information of parsed stream names. /// -public struct ParsedName +/// The parsed title. +/// The parsed tags. +public readonly struct ParsedName(string title, string[] tags) { - /// - /// Initializes a new instance of the struct. - /// - /// The parsed title. - /// The parsed tags. - public ParsedName(string title, string[] tags) - { - Title = title; - Tags = tags; - } - /// /// Gets the parsed title. /// - public string Title { get; init; } + public string Title { get; init; } = title; /// /// Gets the parsed tags. /// - public string[] Tags { get; init; } + public string[] Tags { get; init; } = tags; } diff --git a/Jellyfin.Xtream/Service/Restream.cs b/Jellyfin.Xtream/Service/Restream.cs index c9a095f..30d04d0 100644 --- a/Jellyfin.Xtream/Service/Restream.cs +++ b/Jellyfin.Xtream/Service/Restream.cs @@ -39,8 +39,7 @@ public class Restream : ILiveStream, IDirectStreamProvider, IDisposable /// public const string TunerHost = "Xtream-Restream"; - private static readonly HttpStatusCode[] _redirects = - [ + private static readonly HttpStatusCode[] _redirects = [ HttpStatusCode.Moved, HttpStatusCode.MovedPermanently, HttpStatusCode.PermanentRedirect, diff --git a/Jellyfin.Xtream/Service/StreamService.cs b/Jellyfin.Xtream/Service/StreamService.cs index 214c6c1..c5f6abd 100644 --- a/Jellyfin.Xtream/Service/StreamService.cs +++ b/Jellyfin.Xtream/Service/StreamService.cs @@ -17,7 +17,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -35,7 +34,7 @@ namespace Jellyfin.Xtream.Service; /// /// A service for dealing with stream information. /// -public class StreamService +public partial class StreamService { /// /// The id prefix for VOD category channel items. @@ -92,21 +91,7 @@ public class StreamService /// public const int EpgPrefix = 0x5d774c3f; - private static readonly Regex TagRegex = new Regex(@"\[([^\]]+)\]|\|([^\|]+)\|"); - - private readonly ILogger logger; - private readonly Plugin plugin; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the class. - public StreamService(ILogger logger, Plugin plugin) - { - this.logger = logger; - this.plugin = plugin; - } + private static readonly Regex _tagRegex = TagRegex(); /// /// Parses tags in the name of a stream entry. @@ -122,8 +107,8 @@ public class StreamService /// A struct containing the cleaned title and parsed tags. public static ParsedName ParseName(string name) { - List tags = new List(); - string title = TagRegex.Replace( + List tags = []; + string title = _tagRegex.Replace( name, (match) => { @@ -154,14 +139,13 @@ public class StreamService return new ParsedName { Title = title[stripLength..].Trim(), - Tags = tags.ToArray(), + Tags = [.. tags], }; } private bool IsConfigured(SerializableDictionary> config, int category, int id) { - HashSet? values; - return config.TryGetValue(category, out values) && (values.Count == 0 || values.Contains(id)); + return config.TryGetValue(category, out var values) && (values.Count == 0 || values.Contains(id)); } /// @@ -171,13 +155,13 @@ public class StreamService /// IAsyncEnumerable{StreamInfo}. public async Task> GetLiveStreams(CancellationToken cancellationToken) { - PluginConfiguration config = plugin.Configuration; + PluginConfiguration config = Plugin.Instance.Configuration; using XtreamClient client = new XtreamClient(); IEnumerable>> tasks = config.LiveTv.Select(async (entry) => { int categoryId = entry.Key; - var streams = await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false); + var streams = await client.GetLiveStreamsByCategoryAsync(Plugin.Instance.Creds, categoryId, cancellationToken).ConfigureAwait(false); return streams.Where((StreamInfo channel) => IsConfigured(config.LiveTv, categoryId, channel.StreamId)); }); return (await Task.WhenAll(tasks).ConfigureAwait(false)).SelectMany(i => i); @@ -230,11 +214,9 @@ public class StreamService /// IAsyncEnumerable{StreamInfo}. public async Task> GetVodCategories(CancellationToken cancellationToken) { - using (XtreamClient client = new XtreamClient()) - { - List categories = await client.GetVodCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false); - return categories.Where((Category category) => plugin.Configuration.Vod.ContainsKey(category.CategoryId)); - } + using XtreamClient client = new XtreamClient(); + List categories = await client.GetVodCategoryAsync(Plugin.Instance.Creds, cancellationToken).ConfigureAwait(false); + return categories.Where((Category category) => Plugin.Instance.Configuration.Vod.ContainsKey(category.CategoryId)); } /// @@ -245,16 +227,14 @@ public class StreamService /// IAsyncEnumerable{StreamInfo}. public async Task> GetVodStreams(int categoryId, CancellationToken cancellationToken) { - if (!plugin.Configuration.Vod.ContainsKey(categoryId)) + if (!Plugin.Instance.Configuration.Vod.ContainsKey(categoryId)) { return new List(); } - using (XtreamClient client = new XtreamClient()) - { - List streams = await client.GetVodStreamsByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false); - return streams.Where((StreamInfo stream) => IsConfigured(plugin.Configuration.Vod, categoryId, stream.StreamId)); - } + using XtreamClient client = new XtreamClient(); + List streams = await client.GetVodStreamsByCategoryAsync(Plugin.Instance.Creds, categoryId, cancellationToken).ConfigureAwait(false); + return streams.Where((StreamInfo stream) => IsConfigured(Plugin.Instance.Configuration.Vod, categoryId, stream.StreamId)); } /// @@ -264,12 +244,9 @@ public class StreamService /// IAsyncEnumerable{StreamInfo}. public async Task> GetSeriesCategories(CancellationToken cancellationToken) { - using (XtreamClient client = new XtreamClient()) - { - List categories = await client.GetSeriesCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false); - return categories - .Where((Category category) => plugin.Configuration.Series.ContainsKey(category.CategoryId)); - } + using XtreamClient client = new XtreamClient(); + List categories = await client.GetSeriesCategoryAsync(Plugin.Instance.Creds, cancellationToken).ConfigureAwait(false); + return categories.Where((Category category) => Plugin.Instance.Configuration.Series.ContainsKey(category.CategoryId)); } /// @@ -280,16 +257,14 @@ public class StreamService /// IAsyncEnumerable{StreamInfo}. public async Task> GetSeries(int categoryId, CancellationToken cancellationToken) { - if (!plugin.Configuration.Series.ContainsKey(categoryId)) + if (!Plugin.Instance.Configuration.Series.ContainsKey(categoryId)) { return new List(); } - using (XtreamClient client = new XtreamClient()) - { - List series = await client.GetSeriesByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false); - return series.Where((Series series) => IsConfigured(plugin.Configuration.Series, series.CategoryId, series.SeriesId)); - } + using XtreamClient client = new XtreamClient(); + List series = await client.GetSeriesByCategoryAsync(Plugin.Instance.Creds, categoryId, cancellationToken).ConfigureAwait(false); + return series.Where((Series series) => IsConfigured(Plugin.Instance.Configuration.Series, series.CategoryId, series.SeriesId)); } /// @@ -300,17 +275,15 @@ public class StreamService /// IAsyncEnumerable{StreamInfo}. public async Task>> GetSeasons(int seriesId, CancellationToken cancellationToken) { - using (XtreamClient client = new XtreamClient()) + using XtreamClient client = new XtreamClient(); + SeriesStreamInfo series = await client.GetSeriesStreamsBySeriesAsync(Plugin.Instance.Creds, seriesId, cancellationToken).ConfigureAwait(false); + int categoryId = series.Info.CategoryId; + if (!IsConfigured(Plugin.Instance.Configuration.Series, categoryId, seriesId)) { - SeriesStreamInfo series = await client.GetSeriesStreamsBySeriesAsync(plugin.Creds, seriesId, cancellationToken).ConfigureAwait(false); - int categoryId = series.Info.CategoryId; - if (!IsConfigured(plugin.Configuration.Series, categoryId, seriesId)) - { - return new List>(); - } - - return series.Episodes.Keys.Select((int seasonId) => new Tuple(series, seasonId)); + return new List>(); } + + return series.Episodes.Keys.Select((int seasonId) => new Tuple(series, seasonId)); } /// @@ -322,12 +295,10 @@ public class StreamService /// IAsyncEnumerable{StreamInfo}. public async Task>> GetEpisodes(int seriesId, int seasonId, CancellationToken cancellationToken) { - using (XtreamClient client = new XtreamClient()) - { - SeriesStreamInfo series = await client.GetSeriesStreamsBySeriesAsync(plugin.Creds, seriesId, cancellationToken).ConfigureAwait(false); - Season? season = series.Seasons.FirstOrDefault(s => s.SeasonId == seasonId); - return series.Episodes[seasonId].Select((Episode episode) => new Tuple(series, season, episode)); - } + using XtreamClient client = new XtreamClient(); + SeriesStreamInfo series = await client.GetSeriesStreamsBySeriesAsync(Plugin.Instance.Creds, seriesId, cancellationToken).ConfigureAwait(false); + Season? season = series.Seasons.FirstOrDefault(s => s.SeasonId == seasonId); + return series.Episodes[seasonId].Select((Episode episode) => new Tuple(series, season, episode)); } private static void StoreBytes(byte[] dst, int offset, int i) @@ -362,14 +333,14 @@ public class StreamService /// /// Gets the four 32-bit integers represented in the GUID. /// - /// The input GUID. + /// The input GUID. /// Bytes 0-3. /// Bytes 4-7. /// Bytes 8-11. /// Bytes 12-15. - public static void FromGuid(Guid guid, out int i0, out int i1, out int i2, out int i3) + public static void FromGuid(Guid id, out int i0, out int i1, out int i2, out int i3) { - byte[] tmp = guid.ToByteArray(); + byte[] tmp = id.ToByteArray(); if (BitConverter.IsLittleEndian) { Array.Reverse(tmp); @@ -416,7 +387,7 @@ public class StreamService break; } - PluginConfiguration config = plugin.Configuration; + PluginConfiguration config = Plugin.Instance.Configuration; string uri = $"{config.BaseUrl}{prefix}/{config.Username}/{config.Password}/{id}"; if (!string.IsNullOrEmpty(extension)) { @@ -437,20 +408,19 @@ public class StreamService IsInfiniteStream = isLive, IsRemote = true, // Define media sources with unknown index and interlaced to improve compatibility. - MediaStreams = new MediaStream[] - { - new MediaStream + MediaStreams = [ + new() { Type = MediaStreamType.Video, Index = -1, IsInterlaced = true }, - new MediaStream + new() { Type = MediaStreamType.Audio, Index = -1 } - }, + ], Name = "default", Path = uri, Protocol = MediaProtocol.Http, @@ -461,4 +431,7 @@ public class StreamService SupportsProbing = true, }; } + + [GeneratedRegex(@"\[([^\]]+)\]|\|([^\|]+)\|")] + private static partial Regex TagRegex(); } diff --git a/Jellyfin.Xtream/Service/TaskService.cs b/Jellyfin.Xtream/Service/TaskService.cs index 893f4d4..b7e6623 100644 --- a/Jellyfin.Xtream/Service/TaskService.cs +++ b/Jellyfin.Xtream/Service/TaskService.cs @@ -16,32 +16,15 @@ using System; using System.Linq; using MediaBrowser.Model.Tasks; -using Microsoft.Extensions.Logging; namespace Jellyfin.Xtream.Service; /// /// A service for dealing with stream information. /// -public class TaskService +/// Instance of the interface. +public class TaskService(ITaskManager taskManager) { - private readonly ILogger logger; - private readonly Plugin plugin; - private readonly ITaskManager taskManager; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the class. - /// Instance of the interface. - public TaskService(ILogger logger, Plugin plugin, ITaskManager taskManager) - { - this.logger = logger; - this.plugin = plugin; - this.taskManager = taskManager; - } - private static Type? FindType(string assembly, string fullName) { return AppDomain.CurrentDomain.GetAssemblies() @@ -60,16 +43,12 @@ public class TaskService /// If the task type is not found. public void CancelIfRunningAndQueue(string assembly, string fullName) { - Type? refreshType = FindType(assembly, fullName); - if (refreshType == null) - { - throw new ArgumentException("Refresh task not found"); - } + Type refreshType = FindType(assembly, fullName) ?? throw new ArgumentException("Refresh task not found"); // As the type is not publicly visible, use reflection. typeof(ITaskManager) - .GetMethod(nameof(ITaskManager.CancelIfRunningAndQueue), 1, Array.Empty())? + .GetMethod(nameof(ITaskManager.CancelIfRunningAndQueue), 1, [])? .MakeGenericMethod(refreshType)? - .Invoke(taskManager, Array.Empty()); + .Invoke(taskManager, []); } } diff --git a/Jellyfin.Xtream/Service/WrappedBufferReadStream.cs b/Jellyfin.Xtream/Service/WrappedBufferReadStream.cs index bd0c790..24dd772 100644 --- a/Jellyfin.Xtream/Service/WrappedBufferReadStream.cs +++ b/Jellyfin.Xtream/Service/WrappedBufferReadStream.cs @@ -24,10 +24,9 @@ namespace Jellyfin.Xtream.Service; /// public class WrappedBufferReadStream : Stream { - private readonly WrappedBufferStream sourceBuffer; + private readonly WrappedBufferStream _sourceBuffer; - private readonly long initialReadHead; - private long readHead; + private readonly long _initialReadHead; /// /// Initializes a new instance of the class. @@ -35,25 +34,25 @@ public class WrappedBufferReadStream : Stream /// The source buffer to read from. public WrappedBufferReadStream(WrappedBufferStream sourceBuffer) { - this.sourceBuffer = sourceBuffer; - this.readHead = sourceBuffer.TotalBytesWritten; - this.initialReadHead = readHead; + _sourceBuffer = sourceBuffer; + _initialReadHead = sourceBuffer.TotalBytesWritten; + ReadHead = _initialReadHead; } /// /// Gets the virtual position in the source buffer. /// - public long ReadHead { get => readHead; } + public long ReadHead { get; private set; } /// /// Gets the number of bytes that have been written to this stream. /// - public long TotalBytesRead { get => readHead - initialReadHead; } + public long TotalBytesRead { get => ReadHead - _initialReadHead; } /// public override long Position { - get => readHead % sourceBuffer.BufferSize; set { } + get => ReadHead % _sourceBuffer.BufferSize; set { } } /// @@ -73,16 +72,16 @@ public class WrappedBufferReadStream : Stream /// public override int Read(byte[] buffer, int offset, int count) { - long gap = sourceBuffer.TotalBytesWritten - readHead; + long gap = _sourceBuffer.TotalBytesWritten - ReadHead; // We cannot return with 0 bytes read, as that indicates the end of the stream has been reached while (gap == 0) { Thread.Sleep(1); - gap = sourceBuffer.TotalBytesWritten - readHead; + gap = _sourceBuffer.TotalBytesWritten - ReadHead; } - if (gap > sourceBuffer.BufferSize) + if (gap > _sourceBuffer.BufferSize) { // TODO: design good handling method. // Options: @@ -99,12 +98,12 @@ public class WrappedBufferReadStream : Stream while (read < canCopy) { // The amount of bytes that we can directly write from the current position without wrapping. - long readable = Math.Min(canCopy - read, sourceBuffer.BufferSize - Position); + long readable = Math.Min(canCopy - read, _sourceBuffer.BufferSize - Position); // Copy the data. - Array.Copy(sourceBuffer.Buffer, Position, buffer, offset + read, readable); + Array.Copy(_sourceBuffer.Buffer, Position, buffer, offset + read, readable); read += readable; - readHead += readable; + ReadHead += readable; } return (int)read; diff --git a/Jellyfin.Xtream/Service/WrappedBufferStream.cs b/Jellyfin.Xtream/Service/WrappedBufferStream.cs index e8bf188..5f4448b 100644 --- a/Jellyfin.Xtream/Service/WrappedBufferStream.cs +++ b/Jellyfin.Xtream/Service/WrappedBufferStream.cs @@ -21,43 +21,30 @@ namespace Jellyfin.Xtream.Service; /// /// Stream which writes to a self-overwriting internal buffer. /// -public class WrappedBufferStream : Stream +/// Size in bytes of the internal buffer. +public class WrappedBufferStream(int bufferSize) : Stream { - private readonly byte[] sourceBuffer; - - private long totalBytesWritten; - - /// - /// Initializes a new instance of the class. - /// - /// Size in bytes of the internal buffer. - public WrappedBufferStream(int bufferSize) - { - this.sourceBuffer = new byte[bufferSize]; - this.totalBytesWritten = 0; - } - /// /// Gets the maximal size in bytes of read/write chunks. /// - public int BufferSize { get => sourceBuffer.Length; } + public int BufferSize { get => Buffer.Length; } #pragma warning disable CA1819 /// /// Gets the internal buffer. /// - public byte[] Buffer { get => sourceBuffer; } + public byte[] Buffer { get; } = new byte[bufferSize]; #pragma warning restore CA1819 /// /// Gets the number of bytes that have been written to this stream. /// - public long TotalBytesWritten { get => totalBytesWritten; } + public long TotalBytesWritten { get; private set; } /// public override long Position { - get => totalBytesWritten % BufferSize; set { } + get => TotalBytesWritten % BufferSize; set { } } /// @@ -69,16 +56,11 @@ public class WrappedBufferStream : Stream /// public override bool CanSeek => false; -#pragma warning disable CA1065 /// - public override long Length { get => throw new NotImplementedException(); } -#pragma warning restore CA1065 + public override long Length => throw new NotSupportedException(); /// - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); /// public override void Write(byte[] buffer, int offset, int count) @@ -92,23 +74,17 @@ public class WrappedBufferStream : Stream long writable = Math.Min(count - written, BufferSize - Position); // Copy the data. - Array.Copy(buffer, offset + written, sourceBuffer, Position, writable); + Array.Copy(buffer, offset + written, Buffer, Position, writable); written += writable; - totalBytesWritten += writable; + TotalBytesWritten += writable; } } /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); /// - public override void SetLength(long value) - { - throw new NotImplementedException(); - } + public override void SetLength(long value) => throw new NotSupportedException(); /// public override void Flush() diff --git a/Jellyfin.Xtream/VodChannel.cs b/Jellyfin.Xtream/VodChannel.cs index 1cca215..b2211f9 100644 --- a/Jellyfin.Xtream/VodChannel.cs +++ b/Jellyfin.Xtream/VodChannel.cs @@ -33,19 +33,9 @@ namespace Jellyfin.Xtream; /// /// The Xtream Codes API channel. /// -public class VodChannel : IChannel +/// Instance of the interface. +public class VodChannel(ILogger logger) : IChannel { - private readonly ILogger logger; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - public VodChannel(ILogger logger) - { - this.logger = logger; - } - /// public string? Name => "Xtream Video On-Demand"; @@ -64,17 +54,14 @@ public class VodChannel : IChannel /// public InternalChannelFeatures GetChannelFeatures() { - return new InternalChannelFeatures + return new() { - ContentTypes = new List - { + ContentTypes = [ ChannelMediaContentType.Movie, - }, - - MediaTypes = new List - { + ], + MediaTypes = [ ChannelMediaType.Video - }, + ], }; } @@ -130,12 +117,11 @@ public class VodChannel : IChannel { long added = long.Parse(stream.Added, CultureInfo.InvariantCulture); ParsedName parsedName = StreamService.ParseName(stream.Name); - List sources = new List() - { + List sources = [ Plugin.Instance.StreamService.GetMediaSourceInfo(StreamType.Vod, stream.StreamId, stream.ContainerExtension) - }; + ]; - return new ChannelItemInfo() + return new() { ContentType = ChannelMediaContentType.Movie, DateCreated = DateTimeOffset.FromUnixTimeSeconds(added).DateTime, @@ -153,10 +139,10 @@ public class VodChannel : IChannel private async Task GetCategories(CancellationToken cancellationToken) { + IEnumerable categories = await Plugin.Instance.StreamService.GetVodCategories(cancellationToken).ConfigureAwait(false); List items = new List( - (await Plugin.Instance.StreamService.GetVodCategories(cancellationToken).ConfigureAwait(false)) - .Select((Category category) => StreamService.CreateChannelItemInfo(StreamService.VodCategoryPrefix, category))); - return new ChannelItemResult() + categories.Select((Category category) => StreamService.CreateChannelItemInfo(StreamService.VodCategoryPrefix, category))); + return new() { Items = items, TotalRecordCount = items.Count @@ -165,10 +151,9 @@ public class VodChannel : IChannel private async Task GetStreams(int categoryId, CancellationToken cancellationToken) { - List items = new List( - (await Plugin.Instance.StreamService.GetVodStreams(categoryId, cancellationToken).ConfigureAwait(false)) - .Select((StreamInfo stream) => CreateChannelItemInfo(stream))); - ChannelItemResult result = new ChannelItemResult() + IEnumerable streams = await Plugin.Instance.StreamService.GetVodStreams(categoryId, cancellationToken).ConfigureAwait(false); + List items = new List(streams.Select(CreateChannelItemInfo)); + ChannelItemResult result = new() { Items = items, TotalRecordCount = items.Count