diff --git a/Jellyfin.Xtream/Api/XtreamController.cs b/Jellyfin.Xtream/Api/XtreamController.cs index d887520..2a0b280 100644 --- a/Jellyfin.Xtream/Api/XtreamController.cs +++ b/Jellyfin.Xtream/Api/XtreamController.cs @@ -203,12 +203,8 @@ public class XtreamController : ControllerBase [HttpGet("LiveTv")] public async Task>> GetLiveTvChannels(CancellationToken cancellationToken) { - List channels = new List(); - await foreach (StreamInfo stream in Plugin.Instance.StreamService.GetLiveStreams(cancellationToken)) - { - channels.Add(CreateChannelResponse(stream)); - } - + IEnumerable streams = await Plugin.Instance.StreamService.GetLiveStreams(cancellationToken).ConfigureAwait(false); + var channels = streams.Select(CreateChannelResponse).ToList(); return Ok(channels); } } diff --git a/Jellyfin.Xtream/CatchupChannel.cs b/Jellyfin.Xtream/CatchupChannel.cs index 39faff9..2d082e2 100644 --- a/Jellyfin.Xtream/CatchupChannel.cs +++ b/Jellyfin.Xtream/CatchupChannel.cs @@ -129,7 +129,7 @@ public class CatchupChannel : IChannel { Plugin plugin = Plugin.Instance; List items = new List(); - await foreach (StreamInfo channel in plugin.StreamService.GetLiveStreamsWithOverrides(cancellationToken)) + foreach (StreamInfo channel in await plugin.StreamService.GetLiveStreamsWithOverrides(cancellationToken).ConfigureAwait(false)) { if (!channel.TvArchive) { diff --git a/Jellyfin.Xtream/LiveTvService.cs b/Jellyfin.Xtream/LiveTvService.cs index dce4fc9..5307d9e 100644 --- a/Jellyfin.Xtream/LiveTvService.cs +++ b/Jellyfin.Xtream/LiveTvService.cs @@ -68,7 +68,7 @@ public class LiveTvService : ILiveTvService, ISupportsDirectStreamProvider { Plugin plugin = Plugin.Instance; List items = new List(); - await foreach (StreamInfo channel in plugin.StreamService.GetLiveStreamsWithOverrides(cancellationToken)) + foreach (StreamInfo channel in await plugin.StreamService.GetLiveStreamsWithOverrides(cancellationToken).ConfigureAwait(false)) { ParsedName parsed = StreamService.ParseName(channel.Name); items.Add(new ChannelInfo() diff --git a/Jellyfin.Xtream/Service/Restream.cs b/Jellyfin.Xtream/Service/Restream.cs index 6af07c9..b3e2212 100644 --- a/Jellyfin.Xtream/Service/Restream.cs +++ b/Jellyfin.Xtream/Service/Restream.cs @@ -39,13 +39,13 @@ public class Restream : ILiveStream, IDisposable /// public const string TunerHost = "Xtream-Restream"; - private static readonly HttpStatusCode[] Redirects = new[] - { + private static readonly HttpStatusCode[] Redirects = + [ HttpStatusCode.Moved, HttpStatusCode.MovedPermanently, HttpStatusCode.PermanentRedirect, HttpStatusCode.Redirect, - }; + ]; private readonly IServerApplicationHost appHost; private readonly WrappedBufferStream buffer; @@ -73,17 +73,18 @@ public class Restream : ILiveStream, IDisposable public Restream(IServerApplicationHost appHost, IHttpClientFactory httpClientFactory, ILogger logger, MediaSourceInfo mediaSource) { this.appHost = appHost; - this.buffer = new WrappedBufferStream(16777216); // 16MiB this.httpClientFactory = httpClientFactory; this.logger = logger; - this.tokenSource = new CancellationTokenSource(); - this.mediaSource = mediaSource; - this.originalStreamId = mediaSource.Id; - this.enableStreamSharing = true; - this.uniqueId = Guid.NewGuid().ToString(); - this.uri = mediaSource.Path; + buffer = new WrappedBufferStream(16777216); // 16MiB + tokenSource = new CancellationTokenSource(); + + originalStreamId = mediaSource.Id; + enableStreamSharing = true; + uniqueId = Guid.NewGuid().ToString(); + + uri = mediaSource.Path; string path = "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Path = appHost.GetSmartApiUrl(IPAddress.Any) + path; MediaSource.EncoderPath = appHost.GetApiUrlForLocalAccess() + path; @@ -118,19 +119,19 @@ public class Restream : ILiveStream, IDisposable } string channelId = mediaSource.Id; - this.logger.LogInformation("Starting restream for channel {ChannelId}.", channelId); + logger.LogInformation("Starting restream for channel {ChannelId}.", channelId); // Response stream is disposed manually. - HttpResponseMessage response = await this.httpClientFactory.CreateClient(NamedClient.Default) + HttpResponseMessage response = await httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None) .ConfigureAwait(true); - this.logger.LogDebug("Stream for channel {ChannelId} using url {Url}", channelId, uri); + logger.LogDebug("Stream for channel {ChannelId} using url {Url}", channelId, uri); // Handle a manual redirect in the case of a HTTPS to HTTP downgrade. if (Redirects.Contains(response.StatusCode)) { - this.logger.LogDebug("Stream for channel {ChannelId} redirected to url {Url}", channelId, response.Headers.Location); - response = await this.httpClientFactory.CreateClient(NamedClient.Default) + logger.LogDebug("Stream for channel {ChannelId} redirected to url {Url}", channelId, response.Headers.Location); + response = await httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(response.Headers.Location, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None) .ConfigureAwait(true); } @@ -140,7 +141,7 @@ public class Restream : ILiveStream, IDisposable .ContinueWith( (Task t) => { - this.logger.LogInformation("Restream for channel {ChannelId} finished with state {Status}", mediaSource.Id, t.Status); + logger.LogInformation("Restream for channel {ChannelId} finished with state {Status}", mediaSource.Id, t.Status); inputStream.Close(); inputStream = null; }, @@ -166,13 +167,13 @@ public class Restream : ILiveStream, IDisposable { if (inputStream == null) { - this.logger.LogInformation("Restream for channel {ChannelId} was not opened.", mediaSource.Id); - Task open = Open(CancellationToken.None); + logger.LogInformation("Restream for channel {ChannelId} was not opened.", mediaSource.Id); + _ = Open(CancellationToken.None); } consumerCount++; - this.logger.LogInformation("Opening restream {Count} for channel {ChannelId}.", consumerCount, mediaSource.Id); - return new WrappedBufferReadStream(this.buffer); + logger.LogInformation("Opening restream {Count} for channel {ChannelId}.", consumerCount, mediaSource.Id); + return new WrappedBufferReadStream(buffer); } /// @@ -183,9 +184,9 @@ public class Restream : ILiveStream, IDisposable { if (disposing) { - this.inputStream?.Dispose(); - this.buffer.Dispose(); - this.tokenSource.Dispose(); + inputStream?.Dispose(); + buffer.Dispose(); + tokenSource.Dispose(); } } diff --git a/Jellyfin.Xtream/Service/StreamService.cs b/Jellyfin.Xtream/Service/StreamService.cs index 95f4cc5..214c6c1 100644 --- a/Jellyfin.Xtream/Service/StreamService.cs +++ b/Jellyfin.Xtream/Service/StreamService.cs @@ -169,24 +169,18 @@ public class StreamService /// /// The cancellation token. /// IAsyncEnumerable{StreamInfo}. - public async IAsyncEnumerable GetLiveStreams([EnumeratorCancellation] CancellationToken cancellationToken) + public async Task> GetLiveStreams(CancellationToken cancellationToken) { PluginConfiguration config = plugin.Configuration; - using (XtreamClient client = new XtreamClient()) - { - foreach (var entry in config.LiveTv) - { - int categoryId = entry.Key; - cancellationToken.ThrowIfCancellationRequested(); + using XtreamClient client = new XtreamClient(); - IEnumerable channels = await client.GetLiveStreamsByCategoryAsync(plugin.Creds, categoryId, cancellationToken).ConfigureAwait(false); - foreach (StreamInfo channel in channels.Where((StreamInfo channel) => IsConfigured(config.LiveTv, categoryId, channel.StreamId))) - { - // If the set is empty, include all channels for the category. - yield return channel; - } - } - } + IEnumerable>> tasks = config.LiveTv.Select(async (entry) => + { + int categoryId = entry.Key; + var streams = await client.GetLiveStreamsByCategoryAsync(plugin.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); } /// @@ -194,10 +188,11 @@ public class StreamService /// /// The cancellation token. /// IAsyncEnumerable{StreamInfo}. - public async IAsyncEnumerable GetLiveStreamsWithOverrides([EnumeratorCancellation] CancellationToken cancellationToken) + public async Task> GetLiveStreamsWithOverrides(CancellationToken cancellationToken) { PluginConfiguration config = Plugin.Instance.Configuration; - await foreach (StreamInfo stream in GetLiveStreams(cancellationToken)) + IEnumerable streams = await GetLiveStreams(cancellationToken).ConfigureAwait(false); + return streams.Select((StreamInfo stream) => { if (config.LiveTvOverrides.TryGetValue(stream.StreamId, out ChannelOverrides? overrides)) { @@ -206,8 +201,8 @@ public class StreamService stream.StreamIcon = overrides.LogoUrl ?? stream.StreamIcon; } - yield return stream; - } + return stream; + }); } ///