Solve most code style warnings #149
@@ -35,26 +35,15 @@ namespace Jellyfin.Xtream.Api;
|
||||
[Produces(MediaTypeNames.Application.Json)]
|
||||
public class XtreamController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<XtreamController> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XtreamController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
public XtreamController(ILogger<XtreamController> 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<ActionResult<IEnumerable<CategoryResponse>>> GetLiveCategories(CancellationToken cancellationToken)
|
||||
{
|
||||
Plugin plugin = Plugin.Instance;
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
using XtreamClient client = new XtreamClient();
|
||||
List<Category> categories = await client.GetLiveCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false);
|
||||
return Ok(categories.Select((Category c) => CreateCategoryResponse(c)));
|
||||
}
|
||||
return Ok(categories.Select(CreateCategoryResponse));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,14 +95,12 @@ public class XtreamController : ControllerBase
|
||||
public async Task<ActionResult<IEnumerable<StreamInfo>>> GetLiveStreams(int categoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
Plugin plugin = Plugin.Instance;
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
using XtreamClient client = new XtreamClient();
|
||||
List<StreamInfo> streams = await client.GetLiveStreamsByCategoryAsync(
|
||||
plugin.Creds,
|
||||
categoryId,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
return Ok(streams.Select((StreamInfo s) => CreateItemResponse(s)));
|
||||
}
|
||||
return Ok(streams.Select(CreateItemResponse));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -128,11 +113,9 @@ public class XtreamController : ControllerBase
|
||||
public async Task<ActionResult<IEnumerable<CategoryResponse>>> GetVodCategories(CancellationToken cancellationToken)
|
||||
{
|
||||
Plugin plugin = Plugin.Instance;
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
using XtreamClient client = new XtreamClient();
|
||||
List<Category> categories = await client.GetVodCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false);
|
||||
return Ok(categories.Select((Category c) => CreateCategoryResponse(c)));
|
||||
}
|
||||
return Ok(categories.Select(CreateCategoryResponse));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -146,14 +129,12 @@ public class XtreamController : ControllerBase
|
||||
public async Task<ActionResult<IEnumerable<StreamInfo>>> GetVodStreams(int categoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
Plugin plugin = Plugin.Instance;
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
using XtreamClient client = new XtreamClient();
|
||||
List<StreamInfo> streams = await client.GetVodStreamsByCategoryAsync(
|
||||
plugin.Creds,
|
||||
categoryId,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
return Ok(streams.Select((StreamInfo s) => CreateItemResponse(s)));
|
||||
}
|
||||
return Ok(streams.Select(CreateItemResponse));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -166,11 +147,9 @@ public class XtreamController : ControllerBase
|
||||
public async Task<ActionResult<IEnumerable<CategoryResponse>>> GetSeriesCategories(CancellationToken cancellationToken)
|
||||
{
|
||||
Plugin plugin = Plugin.Instance;
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
using XtreamClient client = new XtreamClient();
|
||||
List<Category> categories = await client.GetSeriesCategoryAsync(plugin.Creds, cancellationToken).ConfigureAwait(false);
|
||||
return Ok(categories.Select((Category c) => CreateCategoryResponse(c)));
|
||||
}
|
||||
return Ok(categories.Select(CreateCategoryResponse));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -184,14 +163,12 @@ public class XtreamController : ControllerBase
|
||||
public async Task<ActionResult<IEnumerable<StreamInfo>>> GetSeriesStreams(int categoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
Plugin plugin = Plugin.Instance;
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
using XtreamClient client = new XtreamClient();
|
||||
List<Series> series = await client.GetSeriesByCategoryAsync(
|
||||
plugin.Creds,
|
||||
categoryId,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
return Ok(series.Select((Series s) => CreateItemResponse(s)));
|
||||
}
|
||||
return Ok(series.Select(CreateItemResponse));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -34,18 +34,10 @@ namespace Jellyfin.Xtream;
|
||||
/// <summary>
|
||||
/// The Xtream Codes API channel.
|
||||
/// </summary>
|
||||
public class CatchupChannel : IChannel
|
||||
{
|
||||
private readonly ILogger<CatchupChannel> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CatchupChannel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
public CatchupChannel(ILogger<CatchupChannel> logger)
|
||||
public class CatchupChannel(ILogger<CatchupChannel> logger) : IChannel
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
private readonly ILogger<CatchupChannel> _logger = logger;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? Name => "Xtream Catch-up";
|
||||
@@ -67,15 +59,12 @@ public class CatchupChannel : IChannel
|
||||
{
|
||||
return new InternalChannelFeatures
|
||||
{
|
||||
ContentTypes = new List<ChannelMediaContentType>
|
||||
{
|
||||
ContentTypes = [
|
||||
ChannelMediaContentType.TvExtra,
|
||||
},
|
||||
|
||||
MediaTypes = new List<ChannelMediaType>
|
||||
{
|
||||
],
|
||||
MediaTypes = [
|
||||
ChannelMediaType.Video
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,13 +79,10 @@ public class CatchupChannel : IChannel
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ImageType> GetSupportedChannelImages()
|
||||
{
|
||||
return new List<ImageType>
|
||||
public IEnumerable<ImageType> GetSupportedChannelImages() => new List<ImageType>
|
||||
{
|
||||
// ImageType.Primary
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ChannelItemResult> 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<ChannelItemResult> GetChannels(CancellationToken cancellationToken)
|
||||
{
|
||||
Plugin plugin = Plugin.Instance;
|
||||
List<ChannelItemInfo> items = new List<ChannelItemInfo>();
|
||||
List<ChannelItemInfo> items = [];
|
||||
foreach (StreamInfo channel in await plugin.StreamService.GetLiveStreamsWithOverrides(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (!channel.TvArchive)
|
||||
@@ -159,23 +145,19 @@ public class CatchupChannel : IChannel
|
||||
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}");
|
||||
}
|
||||
using XtreamClient client = new XtreamClient();
|
||||
|
||||
List<StreamInfo> 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<ChannelItemInfo> items = new List<ChannelItemInfo>();
|
||||
|
||||
List<ChannelItemInfo> items = [];
|
||||
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()
|
||||
items.Add(new()
|
||||
{
|
||||
Id = StreamService.ToGuid(StreamService.CatchupPrefix, channel.CategoryId, channel.StreamId, day).ToString(),
|
||||
ImageUrl = channel.StreamIcon,
|
||||
@@ -185,50 +167,43 @@ public class CatchupChannel : IChannel
|
||||
});
|
||||
}
|
||||
|
||||
ChannelItemResult result = new ChannelItemResult()
|
||||
ChannelItemResult result = new()
|
||||
{
|
||||
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())
|
||||
{
|
||||
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}");
|
||||
}
|
||||
using XtreamClient client = new XtreamClient();
|
||||
|
||||
List<StreamInfo> 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<ChannelItemInfo> items = new List<ChannelItemInfo>();
|
||||
List<ChannelItemInfo> items = [];
|
||||
|
||||
// Create fallback single-stream catch-up if no EPG is available.
|
||||
if (epgs.Listings.Count == 0)
|
||||
{
|
||||
int duration = 24 * 60;
|
||||
return new ChannelItemResult()
|
||||
return new()
|
||||
{
|
||||
Items = new List<ChannelItemInfo>()
|
||||
{
|
||||
new ChannelItemInfo()
|
||||
new()
|
||||
{
|
||||
ContentType = ChannelMediaContentType.TvExtra,
|
||||
Id = StreamService.ToGuid(StreamService.CatchupStreamPrefix, channelId, 0, day).ToString(),
|
||||
IsLiveStream = false,
|
||||
MediaSources = new List<MediaSourceInfo>()
|
||||
{
|
||||
MediaSources = [
|
||||
plugin.StreamService.GetMediaSourceInfo(StreamType.CatchUp, channelId, start: start, durationMinutes: duration)
|
||||
},
|
||||
],
|
||||
MediaType = ChannelMediaType.Video,
|
||||
Name = $"No EPG available",
|
||||
Type = ChannelItemType.Media,
|
||||
@@ -243,12 +218,11 @@ public class CatchupChannel : IChannel
|
||||
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<MediaSourceInfo> sources = new List<MediaSourceInfo>()
|
||||
{
|
||||
List<MediaSourceInfo> sources = [
|
||||
plugin.StreamService.GetMediaSourceInfo(StreamType.CatchUp, channelId, start: epg.StartLocalTime, durationMinutes: durationMinutes)
|
||||
};
|
||||
];
|
||||
|
||||
items.Add(new ChannelItemInfo()
|
||||
items.Add(new()
|
||||
{
|
||||
ContentType = ChannelMediaContentType.TvExtra,
|
||||
DateCreated = epg.Start,
|
||||
@@ -264,14 +238,13 @@ public class CatchupChannel : IChannel
|
||||
});
|
||||
}
|
||||
|
||||
ChannelItemResult result = new ChannelItemResult()
|
||||
ChannelItemResult result = new()
|
||||
{
|
||||
Items = items,
|
||||
TotalRecordCount = items.Count
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabledFor(string userId)
|
||||
|
@@ -18,35 +18,25 @@ namespace Jellyfin.Xtream.Client;
|
||||
/// <summary>
|
||||
/// A wrapper class for Xtream API client connection information.
|
||||
/// </summary>
|
||||
public class ConnectionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseUrl">The base url including protocol and port number, without trailing slash.</param>
|
||||
/// <param name="username">The username for authentication.</param>
|
||||
/// <param name="password">The password for authentication.</param>
|
||||
public ConnectionInfo(string baseUrl, string username, string password)
|
||||
public class ConnectionInfo(string baseUrl, string username, string password)
|
||||
{
|
||||
BaseUrl = baseUrl;
|
||||
UserName = username;
|
||||
Password = password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base url including protocol and port number, without trailing slash.
|
||||
/// </summary>
|
||||
public string BaseUrl { get; set; }
|
||||
public string BaseUrl { get; set; } = baseUrl;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the username for authentication.
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
public string UserName { get; set; } = username;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password for authentication.
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
public string Password { get; set; } = password;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => $"{BaseUrl} {UserName}:{Password}";
|
||||
|
@@ -27,10 +27,12 @@ namespace Jellyfin.Xtream.Client;
|
||||
/// <summary>
|
||||
/// The Xtream API client implementation.
|
||||
/// </summary>
|
||||
public class XtreamClient : IDisposable
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="XtreamClient"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="client">The HTTP client used.</param>
|
||||
public class XtreamClient(HttpClient client) : IDisposable
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XtreamClient"/> class.
|
||||
/// </summary>
|
||||
@@ -38,19 +40,10 @@ public class XtreamClient : IDisposable
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XtreamClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="client">The HTTP client used.</param>
|
||||
public XtreamClient(HttpClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
private async Task<T> QueryApi<T>(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<T>(jsonContent)!;
|
||||
}
|
||||
|
||||
@@ -114,7 +107,7 @@ public class XtreamClient : IDisposable
|
||||
/// <param name="b">Unused.</param>
|
||||
protected virtual void Dispose(bool b)
|
||||
{
|
||||
_client?.Dispose();
|
||||
client?.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@@ -20,13 +20,6 @@ namespace Jellyfin.Xtream.Configuration;
|
||||
/// </summary>
|
||||
public class ChannelOverrides
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChannelOverrides"/> class.
|
||||
/// </summary>
|
||||
public ChannelOverrides()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the TV channel number.
|
||||
/// </summary>
|
||||
|
@@ -24,38 +24,20 @@ namespace Jellyfin.Xtream.Configuration;
|
||||
/// </summary>
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class.
|
||||
/// </summary>
|
||||
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<int, HashSet<int>>();
|
||||
Vod = new SerializableDictionary<int, HashSet<int>>();
|
||||
Series = new SerializableDictionary<int, HashSet<int>>();
|
||||
LiveTvOverrides = new SerializableDictionary<int, ChannelOverrides>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base url including protocol and trailing slash.
|
||||
/// </summary>
|
||||
public string BaseUrl { get; set; }
|
||||
public string BaseUrl { get; set; } = "https://example.com";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the username.
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password.
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the Catch-up channel is visible.
|
||||
@@ -75,21 +57,21 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
/// <summary>
|
||||
/// Gets or sets the channels displayed in Live TV.
|
||||
/// </summary>
|
||||
public SerializableDictionary<int, HashSet<int>> LiveTv { get; set; }
|
||||
public SerializableDictionary<int, HashSet<int>> LiveTv { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the streams displayed in VOD.
|
||||
/// </summary>
|
||||
public SerializableDictionary<int, HashSet<int>> Vod { get; set; }
|
||||
public SerializableDictionary<int, HashSet<int>> Vod { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the streams displayed in Series.
|
||||
/// </summary>
|
||||
public SerializableDictionary<int, HashSet<int>> Series { get; set; }
|
||||
public SerializableDictionary<int, HashSet<int>> Series { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channel override configuration for Live TV.
|
||||
/// </summary>
|
||||
public SerializableDictionary<int, ChannelOverrides> LiveTvOverrides { get; set; }
|
||||
public SerializableDictionary<int, ChannelOverrides> LiveTvOverrides { get; set; } = [];
|
||||
}
|
||||
#pragma warning restore CA2227
|
||||
|
@@ -32,15 +32,15 @@ namespace Jellyfin.Xtream.Configuration;
|
||||
public sealed class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, 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));
|
||||
|
||||
/// <summary>Initializes a new instance of the
|
||||
/// <see cref="SerializableDictionary<TKey, TValue>"/> class.
|
||||
@@ -49,12 +49,6 @@ where TKey : notnull
|
||||
{
|
||||
}
|
||||
|
||||
private string ItemTagName => DefaultItemTag;
|
||||
|
||||
private string KeyTagName => DefaultKeyTag;
|
||||
|
||||
private string ValueTagName => DefaultValueTag;
|
||||
|
||||
/// <inheritdoc />
|
||||
public XmlSchema? GetSchema()
|
||||
{
|
||||
@@ -91,7 +85,7 @@ where TKey : notnull
|
||||
{
|
||||
foreach (var keyValuePair in this)
|
||||
{
|
||||
WriteItem(writer, keyValuePair);
|
||||
SerializableDictionary<TKey, TValue>.WriteItem(writer, keyValuePair);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,10 +95,10 @@ where TKey : notnull
|
||||
/// <param name="reader">The XML representation of the object.</param>
|
||||
private void ReadItem(XmlReader reader)
|
||||
{
|
||||
reader.ReadStartElement(ItemTagName);
|
||||
reader.ReadStartElement(ItemTag);
|
||||
try
|
||||
{
|
||||
Add(ReadKey(reader), ReadValue(reader));
|
||||
Add(SerializableDictionary<TKey, TValue>.ReadKey(reader), SerializableDictionary<TKey, TValue>.ReadValue(reader));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -117,17 +111,12 @@ where TKey : notnull
|
||||
/// </summary>
|
||||
/// <param name="reader">The XML representation of the object.</param>
|
||||
/// <returns>The dictionary item's key.</returns>
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="reader">The XML representation of the object.</param>
|
||||
/// <returns>The dictionary item's value.</returns>
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="writer">The XML writer to serialize to.</param>
|
||||
/// <param name="keyValuePair">The key/value pair.</param>
|
||||
private void WriteItem(XmlWriter writer, KeyValuePair<TKey, TValue> keyValuePair)
|
||||
private static void WriteItem(XmlWriter writer, KeyValuePair<TKey, TValue> keyValuePair)
|
||||
{
|
||||
writer.WriteStartElement(ItemTagName);
|
||||
writer.WriteStartElement(ItemTag);
|
||||
try
|
||||
{
|
||||
WriteKey(writer, keyValuePair.Key);
|
||||
WriteValue(writer, keyValuePair.Value);
|
||||
SerializableDictionary<TKey, TValue>.WriteKey(writer, keyValuePair.Key);
|
||||
SerializableDictionary<TKey, TValue>.WriteValue(writer, keyValuePair.Value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -184,12 +168,12 @@ where TKey : notnull
|
||||
/// </summary>
|
||||
/// <param name="writer">The XML writer to serialize to.</param>
|
||||
/// <param name="key">The dictionary item's key.</param>
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="writer">The XML writer to serialize to.</param>
|
||||
/// <param name="value">The dictionary item's value.</param>
|
||||
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
|
||||
{
|
||||
|
@@ -35,28 +35,15 @@ namespace Jellyfin.Xtream;
|
||||
/// <summary>
|
||||
/// Class LiveTvService.
|
||||
/// </summary>
|
||||
public class LiveTvService : ILiveTvService, ISupportsDirectStreamProvider
|
||||
{
|
||||
private readonly IServerApplicationHost appHost;
|
||||
private readonly IHttpClientFactory httpClientFactory;
|
||||
private readonly ILogger<LiveTvService> logger;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
|
||||
/// <summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="LiveTvService"/> class.
|
||||
/// </summary>
|
||||
/// </remarks>
|
||||
/// <param name="appHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param>
|
||||
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
/// <param name="memoryCache">Instance of the <see cref="IMemoryCache"/> interface.</param>
|
||||
public LiveTvService(IServerApplicationHost appHost, IHttpClientFactory httpClientFactory, ILogger<LiveTvService> logger, IMemoryCache memoryCache)
|
||||
public class LiveTvService(IServerApplicationHost appHost, IHttpClientFactory httpClientFactory, ILogger<LiveTvService> logger, IMemoryCache memoryCache) : ILiveTvService, ISupportsDirectStreamProvider
|
||||
{
|
||||
this.appHost = appHost;
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.logger = logger;
|
||||
this.memoryCache = memoryCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Xtream Live";
|
||||
|
||||
@@ -67,7 +54,7 @@ public class LiveTvService : ILiveTvService, ISupportsDirectStreamProvider
|
||||
public async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Plugin plugin = Plugin.Instance;
|
||||
List<ChannelInfo> items = new List<ChannelInfo>();
|
||||
List<ChannelInfo> 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<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
|
||||
{
|
||||
MediaSourceInfo source = await GetChannelStream(channelId, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
return new List<MediaSourceInfo>() { source };
|
||||
return [source];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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,
|
||||
|
@@ -34,24 +34,20 @@ namespace Jellyfin.Xtream;
|
||||
/// </summary>
|
||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
{
|
||||
private static Plugin? instance;
|
||||
|
||||
private readonly ILogger<Plugin> _logger;
|
||||
private static Plugin? _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
||||
|
||||
/// </summary>
|
||||
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
||||
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
/// <param name="taskManager">Instance of the <see cref="ITaskManager"/> interface.</param>
|
||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, ILogger<Plugin> 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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -63,10 +59,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
/// <summary>
|
||||
/// Gets the Xtream connection info with credentials.
|
||||
/// </summary>
|
||||
public ConnectionInfo Creds
|
||||
{
|
||||
get => new ConnectionInfo(Configuration.BaseUrl, Configuration.Username, Configuration.Password);
|
||||
}
|
||||
public ConnectionInfo Creds => new(Configuration.BaseUrl, Configuration.Username, Configuration.Password);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data version used to trigger a cache invalidation on plugin update or config change.
|
||||
@@ -76,18 +69,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
/// <summary>
|
||||
/// Gets the current plugin instance.
|
||||
/// </summary>
|
||||
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");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream service instance.
|
||||
@@ -99,7 +81,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
/// </summary>
|
||||
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(
|
||||
|
@@ -34,19 +34,9 @@ namespace Jellyfin.Xtream;
|
||||
/// <summary>
|
||||
/// The Xtream Codes API channel.
|
||||
/// </summary>
|
||||
public class SeriesChannel : IChannel
|
||||
{
|
||||
private readonly ILogger<SeriesChannel> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeriesChannel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
public SeriesChannel(ILogger<SeriesChannel> logger)
|
||||
public class SeriesChannel(ILogger<SeriesChannel> logger) : IChannel
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? Name => "Xtream Series";
|
||||
|
||||
@@ -67,15 +57,13 @@ public class SeriesChannel : IChannel
|
||||
{
|
||||
return new InternalChannelFeatures
|
||||
{
|
||||
ContentTypes = new List<ChannelMediaContentType>
|
||||
{
|
||||
ContentTypes = [
|
||||
ChannelMediaContentType.Episode,
|
||||
},
|
||||
],
|
||||
|
||||
MediaTypes = new List<ChannelMediaType>
|
||||
{
|
||||
MediaTypes = [
|
||||
ChannelMediaType.Video
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,12 +143,12 @@ public class SeriesChannel : IChannel
|
||||
};
|
||||
}
|
||||
|
||||
private List<string> GetGenres(string genreString)
|
||||
private static List<string> GetGenres(string genreString)
|
||||
{
|
||||
return new List<string>(genreString.Split(',').Select(genre => genre.Trim()));
|
||||
return new(genreString.Split(',').Select(genre => genre.Trim()));
|
||||
}
|
||||
|
||||
private List<PersonInfo> GetPeople(string cast)
|
||||
private static List<PersonInfo> 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<string> tags = new List<string>();
|
||||
List<string> 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<MediaSourceInfo> sources = new List<MediaSourceInfo>()
|
||||
{
|
||||
List<MediaSourceInfo> 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<string>(parsedName.Tags),
|
||||
Tags = new(parsedName.Tags),
|
||||
Type = ChannelItemType.Media,
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<ChannelItemResult> GetCategories(CancellationToken cancellationToken)
|
||||
{
|
||||
List<ChannelItemInfo> items = new List<ChannelItemInfo>(
|
||||
(await Plugin.Instance.StreamService.GetSeriesCategories(cancellationToken).ConfigureAwait(false))
|
||||
.Select((Category category) => StreamService.CreateChannelItemInfo(StreamService.SeriesCategoryPrefix, category)));
|
||||
return new ChannelItemResult()
|
||||
IEnumerable<Category> categories = await Plugin.Instance.StreamService.GetSeriesCategories(cancellationToken).ConfigureAwait(false);
|
||||
List<ChannelItemInfo> 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<ChannelItemResult> GetSeries(int categoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
List<ChannelItemInfo> items = new List<ChannelItemInfo>(
|
||||
(await Plugin.Instance.StreamService.GetSeries(categoryId, cancellationToken).ConfigureAwait(false))
|
||||
.Select((Series series) => CreateChannelItemInfo(series)));
|
||||
return new ChannelItemResult()
|
||||
IEnumerable<Series> series = await Plugin.Instance.StreamService.GetSeries(categoryId, cancellationToken).ConfigureAwait(false);
|
||||
List<ChannelItemInfo> items = new(series.Select(CreateChannelItemInfo));
|
||||
return new()
|
||||
{
|
||||
Items = items,
|
||||
TotalRecordCount = items.Count
|
||||
@@ -270,10 +256,10 @@ public class SeriesChannel : IChannel
|
||||
|
||||
private async Task<ChannelItemResult> GetSeasons(int seriesId, CancellationToken cancellationToken)
|
||||
{
|
||||
List<ChannelItemInfo> items = new List<ChannelItemInfo>(
|
||||
(await Plugin.Instance.StreamService.GetSeasons(seriesId, cancellationToken).ConfigureAwait(false))
|
||||
.Select((Tuple<SeriesStreamInfo, int> tuple) => CreateChannelItemInfo(seriesId, tuple.Item1, tuple.Item2)));
|
||||
return new ChannelItemResult()
|
||||
IEnumerable<Tuple<SeriesStreamInfo, int>> seasons = await Plugin.Instance.StreamService.GetSeasons(seriesId, cancellationToken).ConfigureAwait(false);
|
||||
List<ChannelItemInfo> items = new(
|
||||
seasons.Select((Tuple<SeriesStreamInfo, int> 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<ChannelItemResult> GetEpisodes(int seriesId, int seasonId, CancellationToken cancellationToken)
|
||||
{
|
||||
IEnumerable<Tuple<SeriesStreamInfo, Season?, Episode>> episodes = await Plugin.Instance.StreamService.GetEpisodes(seriesId, seasonId, cancellationToken).ConfigureAwait(false);
|
||||
List<ChannelItemInfo> items = new List<ChannelItemInfo>(
|
||||
(await Plugin.Instance.StreamService.GetEpisodes(seriesId, seasonId, cancellationToken).ConfigureAwait(false))
|
||||
.Select((Tuple<SeriesStreamInfo, Season?, Episode> tuple) => CreateChannelItemInfo(tuple.Item1, tuple.Item2, tuple.Item3)));
|
||||
return new ChannelItemResult()
|
||||
episodes.Select((Tuple<SeriesStreamInfo, Season?, Episode> tuple) => CreateChannelItemInfo(tuple.Item1, tuple.Item2, tuple.Item3)));
|
||||
return new()
|
||||
{
|
||||
Items = items,
|
||||
TotalRecordCount = items.Count
|
||||
|
@@ -20,26 +20,17 @@ namespace Jellyfin.Xtream.Service;
|
||||
/// <summary>
|
||||
/// A struct which holds information of parsed stream names.
|
||||
/// </summary>
|
||||
public struct ParsedName
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParsedName"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="title">The parsed title.</param>
|
||||
/// <param name="tags">The parsed tags.</param>
|
||||
public ParsedName(string title, string[] tags)
|
||||
public readonly struct ParsedName(string title, string[] tags)
|
||||
{
|
||||
Title = title;
|
||||
Tags = tags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parsed title.
|
||||
/// </summary>
|
||||
public string Title { get; init; }
|
||||
public string Title { get; init; } = title;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parsed tags.
|
||||
/// </summary>
|
||||
public string[] Tags { get; init; }
|
||||
public string[] Tags { get; init; } = tags;
|
||||
}
|
||||
|
@@ -39,8 +39,7 @@ public class Restream : ILiveStream, IDirectStreamProvider, IDisposable
|
||||
/// </summary>
|
||||
public const string TunerHost = "Xtream-Restream";
|
||||
|
||||
private static readonly HttpStatusCode[] _redirects =
|
||||
[
|
||||
private static readonly HttpStatusCode[] _redirects = [
|
||||
HttpStatusCode.Moved,
|
||||
HttpStatusCode.MovedPermanently,
|
||||
HttpStatusCode.PermanentRedirect,
|
||||
|
@@ -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;
|
||||
/// <summary>
|
||||
/// A service for dealing with stream information.
|
||||
/// </summary>
|
||||
public class StreamService
|
||||
public partial class StreamService
|
||||
{
|
||||
/// <summary>
|
||||
/// The id prefix for VOD category channel items.
|
||||
@@ -92,21 +91,7 @@ public class StreamService
|
||||
/// </summary>
|
||||
public const int EpgPrefix = 0x5d774c3f;
|
||||
|
||||
private static readonly Regex TagRegex = new Regex(@"\[([^\]]+)\]|\|([^\|]+)\|");
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly Plugin plugin;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
/// <param name="plugin">Instance of the <see cref="Plugin"/> class.</param>
|
||||
public StreamService(ILogger logger, Plugin plugin)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
private static readonly Regex _tagRegex = TagRegex();
|
||||
|
||||
/// <summary>
|
||||
/// Parses tags in the name of a stream entry.
|
||||
@@ -122,8 +107,8 @@ public class StreamService
|
||||
/// <returns>A <see cref="ParsedName"/> struct containing the cleaned title and parsed tags.</returns>
|
||||
public static ParsedName ParseName(string name)
|
||||
{
|
||||
List<string> tags = new List<string>();
|
||||
string title = TagRegex.Replace(
|
||||
List<string> 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<int, HashSet<int>> config, int category, int id)
|
||||
{
|
||||
HashSet<int>? 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -171,13 +155,13 @@ public class StreamService
|
||||
/// <returns>IAsyncEnumerable{StreamInfo}.</returns>
|
||||
public async Task<IEnumerable<StreamInfo>> GetLiveStreams(CancellationToken cancellationToken)
|
||||
{
|
||||
PluginConfiguration config = plugin.Configuration;
|
||||
PluginConfiguration config = Plugin.Instance.Configuration;
|
||||
using XtreamClient client = new XtreamClient();
|
||||
|
||||
IEnumerable<Task<IEnumerable<StreamInfo>>> 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
|
||||
/// <returns>IAsyncEnumerable{StreamInfo}.</returns>
|
||||
public async Task<IEnumerable<Category>> GetVodCategories(CancellationToken cancellationToken)
|
||||
{
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
List<Category> 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<Category> categories = await client.GetVodCategoryAsync(Plugin.Instance.Creds, cancellationToken).ConfigureAwait(false);
|
||||
return categories.Where((Category category) => Plugin.Instance.Configuration.Vod.ContainsKey(category.CategoryId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -245,16 +227,14 @@ public class StreamService
|
||||
/// <returns>IAsyncEnumerable{StreamInfo}.</returns>
|
||||
public async Task<IEnumerable<StreamInfo>> GetVodStreams(int categoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!plugin.Configuration.Vod.ContainsKey(categoryId))
|
||||
if (!Plugin.Instance.Configuration.Vod.ContainsKey(categoryId))
|
||||
{
|
||||
return new List<StreamInfo>();
|
||||
}
|
||||
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
List<StreamInfo> 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<StreamInfo> streams = await client.GetVodStreamsByCategoryAsync(Plugin.Instance.Creds, categoryId, cancellationToken).ConfigureAwait(false);
|
||||
return streams.Where((StreamInfo stream) => IsConfigured(Plugin.Instance.Configuration.Vod, categoryId, stream.StreamId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -264,12 +244,9 @@ public class StreamService
|
||||
/// <returns>IAsyncEnumerable{StreamInfo}.</returns>
|
||||
public async Task<IEnumerable<Category>> GetSeriesCategories(CancellationToken cancellationToken)
|
||||
{
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
List<Category> 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<Category> categories = await client.GetSeriesCategoryAsync(Plugin.Instance.Creds, cancellationToken).ConfigureAwait(false);
|
||||
return categories.Where((Category category) => Plugin.Instance.Configuration.Series.ContainsKey(category.CategoryId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -280,16 +257,14 @@ public class StreamService
|
||||
/// <returns>IAsyncEnumerable{StreamInfo}.</returns>
|
||||
public async Task<IEnumerable<Series>> GetSeries(int categoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!plugin.Configuration.Series.ContainsKey(categoryId))
|
||||
if (!Plugin.Instance.Configuration.Series.ContainsKey(categoryId))
|
||||
{
|
||||
return new List<Series>();
|
||||
}
|
||||
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
List<Series> 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> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -300,18 +275,16 @@ public class StreamService
|
||||
/// <returns>IAsyncEnumerable{StreamInfo}.</returns>
|
||||
public async Task<IEnumerable<Tuple<SeriesStreamInfo, int>>> GetSeasons(int seriesId, CancellationToken cancellationToken)
|
||||
{
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
SeriesStreamInfo series = await client.GetSeriesStreamsBySeriesAsync(plugin.Creds, seriesId, cancellationToken).ConfigureAwait(false);
|
||||
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.Configuration.Series, categoryId, seriesId))
|
||||
if (!IsConfigured(Plugin.Instance.Configuration.Series, categoryId, seriesId))
|
||||
{
|
||||
return new List<Tuple<SeriesStreamInfo, int>>();
|
||||
}
|
||||
|
||||
return series.Episodes.Keys.Select((int seasonId) => new Tuple<SeriesStreamInfo, int>(series, seasonId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an iterator for the configured seasons in the Series.
|
||||
@@ -322,13 +295,11 @@ public class StreamService
|
||||
/// <returns>IAsyncEnumerable{StreamInfo}.</returns>
|
||||
public async Task<IEnumerable<Tuple<SeriesStreamInfo, Season?, Episode>>> GetEpisodes(int seriesId, int seasonId, CancellationToken cancellationToken)
|
||||
{
|
||||
using (XtreamClient client = new XtreamClient())
|
||||
{
|
||||
SeriesStreamInfo series = await client.GetSeriesStreamsBySeriesAsync(plugin.Creds, seriesId, cancellationToken).ConfigureAwait(false);
|
||||
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<SeriesStreamInfo, Season?, Episode>(series, season, episode));
|
||||
}
|
||||
}
|
||||
|
||||
private static void StoreBytes(byte[] dst, int offset, int i)
|
||||
{
|
||||
@@ -362,14 +333,14 @@ public class StreamService
|
||||
/// <summary>
|
||||
/// Gets the four 32-bit integers represented in the GUID.
|
||||
/// </summary>
|
||||
/// <param name="guid">The input GUID.</param>
|
||||
/// <param name="id">The input GUID.</param>
|
||||
/// <param name="i0">Bytes 0-3.</param>
|
||||
/// <param name="i1">Bytes 4-7.</param>
|
||||
/// <param name="i2">Bytes 8-11.</param>
|
||||
/// <param name="i3">Bytes 12-15.</param>
|
||||
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();
|
||||
}
|
||||
|
@@ -16,32 +16,15 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Xtream.Service;
|
||||
|
||||
/// <summary>
|
||||
/// A service for dealing with stream information.
|
||||
/// </summary>
|
||||
public class TaskService
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly Plugin plugin;
|
||||
private readonly ITaskManager taskManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
/// <param name="plugin">Instance of the <see cref="Plugin"/> class.</param>
|
||||
/// <param name="taskManager">Instance of the <see cref="ITaskManager"/> interface.</param>
|
||||
public TaskService(ILogger logger, Plugin plugin, ITaskManager taskManager)
|
||||
public class TaskService(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
|
||||
/// <exception cref="ArgumentException">If the task type is not found.</exception>
|
||||
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<Type>())?
|
||||
.GetMethod(nameof(ITaskManager.CancelIfRunningAndQueue), 1, [])?
|
||||
.MakeGenericMethod(refreshType)?
|
||||
.Invoke(taskManager, Array.Empty<object>());
|
||||
.Invoke(taskManager, []);
|
||||
}
|
||||
}
|
||||
|
@@ -24,10 +24,9 @@ namespace Jellyfin.Xtream.Service;
|
||||
/// </summary>
|
||||
public class WrappedBufferReadStream : Stream
|
||||
{
|
||||
private readonly WrappedBufferStream sourceBuffer;
|
||||
private readonly WrappedBufferStream _sourceBuffer;
|
||||
|
||||
private readonly long initialReadHead;
|
||||
private long readHead;
|
||||
private readonly long _initialReadHead;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WrappedBufferReadStream"/> class.
|
||||
@@ -35,25 +34,25 @@ public class WrappedBufferReadStream : Stream
|
||||
/// <param name="sourceBuffer">The source buffer to read from.</param>
|
||||
public WrappedBufferReadStream(WrappedBufferStream sourceBuffer)
|
||||
{
|
||||
this.sourceBuffer = sourceBuffer;
|
||||
this.readHead = sourceBuffer.TotalBytesWritten;
|
||||
this.initialReadHead = readHead;
|
||||
_sourceBuffer = sourceBuffer;
|
||||
_initialReadHead = sourceBuffer.TotalBytesWritten;
|
||||
ReadHead = _initialReadHead;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the virtual position in the source buffer.
|
||||
/// </summary>
|
||||
public long ReadHead { get => readHead; }
|
||||
public long ReadHead { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes that have been written to this stream.
|
||||
/// </summary>
|
||||
public long TotalBytesRead { get => readHead - initialReadHead; }
|
||||
public long TotalBytesRead { get => ReadHead - _initialReadHead; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Position
|
||||
{
|
||||
get => readHead % sourceBuffer.BufferSize; set { }
|
||||
get => ReadHead % _sourceBuffer.BufferSize; set { }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -73,16 +72,16 @@ public class WrappedBufferReadStream : Stream
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
|
@@ -21,43 +21,30 @@ namespace Jellyfin.Xtream.Service;
|
||||
/// <summary>
|
||||
/// Stream which writes to a self-overwriting internal buffer.
|
||||
/// </summary>
|
||||
public class WrappedBufferStream : Stream
|
||||
{
|
||||
private readonly byte[] sourceBuffer;
|
||||
|
||||
private long totalBytesWritten;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WrappedBufferStream"/> class.
|
||||
/// </summary>
|
||||
/// <param name="bufferSize">Size in bytes of the internal buffer.</param>
|
||||
public WrappedBufferStream(int bufferSize)
|
||||
public class WrappedBufferStream(int bufferSize) : Stream
|
||||
{
|
||||
this.sourceBuffer = new byte[bufferSize];
|
||||
this.totalBytesWritten = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximal size in bytes of read/write chunks.
|
||||
/// </summary>
|
||||
public int BufferSize { get => sourceBuffer.Length; }
|
||||
public int BufferSize { get => Buffer.Length; }
|
||||
|
||||
#pragma warning disable CA1819
|
||||
/// <summary>
|
||||
/// Gets the internal buffer.
|
||||
/// </summary>
|
||||
public byte[] Buffer { get => sourceBuffer; }
|
||||
public byte[] Buffer { get; } = new byte[bufferSize];
|
||||
#pragma warning restore CA1819
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes that have been written to this stream.
|
||||
/// </summary>
|
||||
public long TotalBytesWritten { get => totalBytesWritten; }
|
||||
public long TotalBytesWritten { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Position
|
||||
{
|
||||
get => totalBytesWritten % BufferSize; set { }
|
||||
get => TotalBytesWritten % BufferSize; set { }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -69,16 +56,11 @@ public class WrappedBufferStream : Stream
|
||||
/// <inheritdoc />
|
||||
public override bool CanSeek => false;
|
||||
|
||||
#pragma warning disable CA1065
|
||||
/// <inheritdoc />
|
||||
public override long Length { get => throw new NotImplementedException(); }
|
||||
#pragma warning restore CA1065
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush()
|
||||
|
@@ -33,19 +33,9 @@ namespace Jellyfin.Xtream;
|
||||
/// <summary>
|
||||
/// The Xtream Codes API channel.
|
||||
/// </summary>
|
||||
public class VodChannel : IChannel
|
||||
{
|
||||
private readonly ILogger<VodChannel> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VodChannel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||
public VodChannel(ILogger<VodChannel> logger)
|
||||
public class VodChannel(ILogger<VodChannel> logger) : IChannel
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? Name => "Xtream Video On-Demand";
|
||||
|
||||
@@ -64,17 +54,14 @@ public class VodChannel : IChannel
|
||||
/// <inheritdoc />
|
||||
public InternalChannelFeatures GetChannelFeatures()
|
||||
{
|
||||
return new InternalChannelFeatures
|
||||
{
|
||||
ContentTypes = new List<ChannelMediaContentType>
|
||||
return new()
|
||||
{
|
||||
ContentTypes = [
|
||||
ChannelMediaContentType.Movie,
|
||||
},
|
||||
|
||||
MediaTypes = new List<ChannelMediaType>
|
||||
{
|
||||
],
|
||||
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<MediaSourceInfo> sources = new List<MediaSourceInfo>()
|
||||
{
|
||||
List<MediaSourceInfo> 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<ChannelItemResult> GetCategories(CancellationToken cancellationToken)
|
||||
{
|
||||
IEnumerable<Category> categories = await Plugin.Instance.StreamService.GetVodCategories(cancellationToken).ConfigureAwait(false);
|
||||
List<ChannelItemInfo> items = new List<ChannelItemInfo>(
|
||||
(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<ChannelItemResult> GetStreams(int categoryId, CancellationToken cancellationToken)
|
||||
{
|
||||
List<ChannelItemInfo> items = new List<ChannelItemInfo>(
|
||||
(await Plugin.Instance.StreamService.GetVodStreams(categoryId, cancellationToken).ConfigureAwait(false))
|
||||
.Select((StreamInfo stream) => CreateChannelItemInfo(stream)));
|
||||
ChannelItemResult result = new ChannelItemResult()
|
||||
IEnumerable<StreamInfo> streams = await Plugin.Instance.StreamService.GetVodStreams(categoryId, cancellationToken).ConfigureAwait(false);
|
||||
List<ChannelItemInfo> items = new List<ChannelItemInfo>(streams.Select(CreateChannelItemInfo));
|
||||
ChannelItemResult result = new()
|
||||
{
|
||||
Items = items,
|
||||
TotalRecordCount = items.Count
|
||||
|
Reference in New Issue
Block a user
Missed 'readonly' opportunity
Field '_instance' can be 'readonly'.
Show more details