Merge pull request #10682 from barronpm/livetv-warnings

Fix some warnings in LiveTV
This commit is contained in:
Bond-009 2023-12-18 23:21:45 +01:00 committed by GitHub
commit 053c3392f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 140 additions and 79 deletions

View File

@ -12,7 +12,7 @@ using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
{ {
public class ExclusiveLiveStream : ILiveStream public sealed class ExclusiveLiveStream : ILiveStream
{ {
private readonly Func<Task> _closeFn; private readonly Func<Task> _closeFn;
@ -51,5 +51,10 @@ namespace Emby.Server.Implementations.Library
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <inheritdoc />
public void Dispose()
{
}
} }
} }

View File

@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
public class DirectRecorder : IRecorder public sealed class DirectRecorder : IRecorder
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
@ -46,7 +46,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
await using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) var output = new FileStream(
targetFile,
FileMode.CreateNew,
FileAccess.Write,
FileShare.Read,
IODefaults.FileStreamBufferSize,
FileOptions.Asynchronous);
await using (output.ConfigureAwait(false))
{ {
onStarted(); onStarted();
@ -80,24 +88,31 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
await using var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.CopyToBufferSize, FileOptions.Asynchronous); var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.CopyToBufferSize, FileOptions.Asynchronous);
await using (output.ConfigureAwait(false))
{
onStarted();
onStarted(); _logger.LogInformation("Copying recording stream to file {0}", targetFile);
_logger.LogInformation("Copying recording stream to file {0}", targetFile); // The media source if infinite so we need to handle stopping ourselves
using var durationToken = new CancellationTokenSource(duration);
using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
cancellationToken = linkedCancellationToken.Token;
// The media source if infinite so we need to handle stopping ourselves await _streamHelper.CopyUntilCancelled(
using var durationToken = new CancellationTokenSource(duration); await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false),
using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); output,
cancellationToken = linkedCancellationToken.Token; IODefaults.CopyToBufferSize,
cancellationToken).ConfigureAwait(false);
await _streamHelper.CopyUntilCancelled( _logger.LogInformation("Recording completed to file {0}", targetFile);
await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), }
output, }
IODefaults.CopyToBufferSize,
cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Recording completed to file {0}", targetFile); /// <inheritdoc />
public void Dispose()
{
} }
} }
} }

View File

@ -37,12 +37,11 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable public sealed class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
{ {
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
@ -74,7 +73,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
private bool _disposed = false; private bool _disposed;
public EmbyTV( public EmbyTV(
IServerApplicationHost appHost, IServerApplicationHost appHost,
@ -1270,7 +1269,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
directStreamProvider = liveStreamResponse.Item2; directStreamProvider = liveStreamResponse.Item2;
} }
var recorder = GetRecorder(mediaStreamInfo); using var recorder = GetRecorder(mediaStreamInfo);
recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
recordPath = EnsureFileUnique(recordPath, timer.Id); recordPath = EnsureFileUnique(recordPath, timer.Id);
@ -2524,22 +2523,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{ {
if (_disposed) if (_disposed)
{ {
return; return;
} }
if (disposing) _recordingDeleteSemaphore.Dispose();
{
_recordingDeleteSemaphore.Dispose();
}
foreach (var pair in _activeRecordings.ToList()) foreach (var pair in _activeRecordings.ToList())
{ {

View File

@ -25,7 +25,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
public class EncodedRecorder : IRecorder, IDisposable public class EncodedRecorder : IRecorder
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
@ -34,10 +34,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private bool _hasExited; private bool _hasExited;
private Stream _logFileStream; private FileStream _logFileStream;
private string _targetPath; private string _targetPath;
private Process _process; private Process _process;
private bool _disposed = false; private bool _disposed;
public EncodedRecorder( public EncodedRecorder(
ILogger logger, ILogger logger,
@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
} }
private async Task StartStreamingLog(Stream source, Stream target) private async Task StartStreamingLog(Stream source, FileStream target)
{ {
try try
{ {

View File

@ -8,7 +8,7 @@ using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
public interface IRecorder public interface IRecorder : IDisposable
{ {
/// <summary> /// <summary>
/// Records the specified media source. /// Records the specified media source.

View File

@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
IsMovie = IsMovie(details), IsMovie = IsMovie(details),
Etag = programInfo.Md5, Etag = programInfo.Md5,
IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase), IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1 IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).Contains("premiere", StringComparison.OrdinalIgnoreCase)
}; };
var showId = programId; var showId = programId;
@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return null; return null;
} }
if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1) if (uri.Contains("http", StringComparison.OrdinalIgnoreCase))
{ {
return uri; return uri;
} }

View File

@ -84,38 +84,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path); _logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false); await using (stream.ConfigureAwait(false))
{
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
}
} }
else else
{ {
await using var stream = AsyncFile.OpenRead(info.Path); var stream = AsyncFile.OpenRead(info.Path);
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false); await using (stream.ConfigureAwait(false))
{
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
}
} }
} }
private async Task<string> UnzipIfNeededAndCopy(string originalUrl, Stream stream, string file, CancellationToken cancellationToken) private async Task<string> UnzipIfNeededAndCopy(string originalUrl, Stream stream, string file, CancellationToken cancellationToken)
{ {
await using var fileStream = new FileStream(file, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); var fileStream = new FileStream(
file,
FileMode.CreateNew,
FileAccess.Write,
FileShare.None,
IODefaults.FileStreamBufferSize,
FileOptions.Asynchronous);
if (Path.GetExtension(originalUrl.AsSpan().LeftPart('?')).Equals(".gz", StringComparison.OrdinalIgnoreCase)) await using (fileStream.ConfigureAwait(false))
{ {
try if (Path.GetExtension(originalUrl.AsSpan().LeftPart('?')).Equals(".gz", StringComparison.OrdinalIgnoreCase))
{ {
using var reader = new GZipStream(stream, CompressionMode.Decompress); try
await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); {
using var reader = new GZipStream(stream, CompressionMode.Decompress);
await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error extracting from gz file {File}", originalUrl);
}
} }
catch (Exception ex) else
{ {
_logger.LogError(ex, "Error extracting from gz file {File}", originalUrl); await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
} }
}
else
{
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
return file; return file;
}
} }
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)

View File

@ -1101,7 +1101,7 @@ namespace Emby.Server.Implementations.LiveTv
progress.Report(100); progress.Report(100);
} }
private async Task<Tuple<List<Guid>, List<Guid>>> RefreshChannelsInternal(ILiveTvService service, IProgress<double> progress, CancellationToken cancellationToken) private async Task<Tuple<List<Guid>, List<Guid>>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress<double> progress, CancellationToken cancellationToken)
{ {
progress.Report(10); progress.Report(10);

View File

@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return list; return list;
} }
protected virtual List<TunerHostInfo> GetTunerHosts() protected virtual IList<TunerHostInfo> GetTunerHosts()
{ {
return GetConfiguration().TunerHosts return GetConfiguration().TunerHosts
.Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)) .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
@ -96,8 +96,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
try try
{ {
Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile)); Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
await using var writeStream = AsyncFile.OpenWrite(channelCacheFile); var writeStream = AsyncFile.OpenWrite(channelCacheFile);
await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false); await using (writeStream.ConfigureAwait(false))
{
await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false);
}
} }
catch (IOException) catch (IOException)
{ {
@ -112,10 +115,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
try try
{ {
await using var readStream = AsyncFile.OpenRead(channelCacheFile); var readStream = AsyncFile.OpenRead(channelCacheFile);
var channels = await JsonSerializer.DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken) await using (readStream.ConfigureAwait(false))
.ConfigureAwait(false); {
list.AddRange(channels); var channels = await JsonSerializer
.DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken)
.ConfigureAwait(false);
list.AddRange(channels);
}
} }
catch (IOException) catch (IOException)
{ {
@ -159,9 +166,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return new List<MediaSourceInfo>(); return new List<MediaSourceInfo>();
} }
protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{ {
ArgumentException.ThrowIfNullOrEmpty(channelId); ArgumentException.ThrowIfNullOrEmpty(channelId);

View File

@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
var model = ModelNumber ?? string.Empty; var model = ModelNumber ?? string.Empty;
if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1) if (model.Contains("hdtc", StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
} }

View File

@ -527,7 +527,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return list; return list;
} }
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{ {
var tunerCount = tunerHost.TunerCount; var tunerCount = tunerHost.TunerCount;

View File

@ -112,6 +112,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return stream; return stream;
} }
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
LiveStreamCancellationTokenSource?.Dispose();
}
}
protected async Task DeleteTempFiles(string path, int retryCount = 0) protected async Task DeleteTempFiles(string path, int retryCount = 0)
{ {
if (retryCount == 0) if (retryCount == 0)
@ -134,7 +149,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
} }
} }
private void TrySeek(Stream stream, long offset) private void TrySeek(FileStream stream, long offset)
{ {
if (!stream.CanSeek) if (!stream.CanSeek)
{ {

View File

@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(list); return Task.FromResult(list);
} }
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{ {
var tunerCount = tunerHost.TunerCount; var tunerCount = tunerHost.TunerCount;

View File

@ -66,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
.ConfigureAwait(false); .ConfigureAwait(false);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStreamAsync(cancellationToken); return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
} }
private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId) private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)

View File

@ -83,14 +83,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath); Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath);
using (response) using (response)
{ {
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await using (stream.ConfigureAwait(false))
await StreamHelper.CopyToAsync( {
stream, var fileStream = new FileStream(
fileStream, TempFilePath,
IODefaults.CopyToBufferSize, FileMode.Create,
() => Resolve(openTaskCompletionSource), FileAccess.Write,
cancellationToken).ConfigureAwait(false); FileShare.Read,
IODefaults.FileStreamBufferSize,
FileOptions.Asynchronous);
await using (fileStream.ConfigureAwait(false))
{
await StreamHelper.CopyToAsync(
stream,
fileStream,
IODefaults.CopyToBufferSize,
() => Resolve(openTaskCompletionSource),
cancellationToken).ConfigureAwait(false);
}
}
} }
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)

View File

@ -2,6 +2,7 @@
#pragma warning disable CA1711, CS1591 #pragma warning disable CA1711, CS1591
using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,7 +10,7 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Library namespace MediaBrowser.Controller.Library
{ {
public interface ILiveStream public interface ILiveStream : IDisposable
{ {
int ConsumerCount { get; set; } int ConsumerCount { get; set; }

View File

@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="currentLiveStreams">The current live streams.</param> /// <param name="currentLiveStreams">The current live streams.</param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param> /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>Live stream wrapped in a task.</returns> /// <returns>Live stream wrapped in a task.</returns>
Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); Task<ILiveStream> GetChannelStream(string channelId, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the channel stream media sources. /// Gets the channel stream media sources.