Merge pull request #10682 from barronpm/livetv-warnings
Fix some warnings in LiveTV
This commit is contained in:
commit
053c3392f4
|
@ -12,7 +12,7 @@ using MediaBrowser.Model.Dto;
|
|||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
public class ExclusiveLiveStream : ILiveStream
|
||||
public sealed class ExclusiveLiveStream : ILiveStream
|
||||
{
|
||||
private readonly Func<Task> _closeFn;
|
||||
|
||||
|
@ -51,5 +51,10 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
public class DirectRecorder : IRecorder
|
||||
public sealed class DirectRecorder : IRecorder
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
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)));
|
||||
|
||||
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();
|
||||
|
||||
|
@ -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)));
|
||||
|
||||
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
|
||||
using var durationToken = new CancellationTokenSource(duration);
|
||||
using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
||||
cancellationToken = linkedCancellationToken.Token;
|
||||
await _streamHelper.CopyUntilCancelled(
|
||||
await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false),
|
||||
output,
|
||||
IODefaults.CopyToBufferSize,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await _streamHelper.CopyUntilCancelled(
|
||||
await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false),
|
||||
output,
|
||||
IODefaults.CopyToBufferSize,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,12 +37,11 @@ using MediaBrowser.Model.IO;
|
|||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
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";
|
||||
|
||||
|
@ -74,7 +73,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private bool _disposed = false;
|
||||
private bool _disposed;
|
||||
|
||||
public EmbyTV(
|
||||
IServerApplicationHost appHost,
|
||||
|
@ -1270,7 +1269,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
directStreamProvider = liveStreamResponse.Item2;
|
||||
}
|
||||
|
||||
var recorder = GetRecorder(mediaStreamInfo);
|
||||
using var recorder = GetRecorder(mediaStreamInfo);
|
||||
|
||||
recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
|
||||
recordPath = EnsureFileUnique(recordPath, timer.Id);
|
||||
|
@ -2524,22 +2523,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_recordingDeleteSemaphore.Dispose();
|
||||
}
|
||||
_recordingDeleteSemaphore.Dispose();
|
||||
|
||||
foreach (var pair in _activeRecordings.ToList())
|
||||
{
|
||||
|
|
|
@ -25,7 +25,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
public class EncodedRecorder : IRecorder, IDisposable
|
||||
public class EncodedRecorder : IRecorder
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
|
@ -34,10 +34,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
||||
private bool _hasExited;
|
||||
private Stream _logFileStream;
|
||||
private FileStream _logFileStream;
|
||||
private string _targetPath;
|
||||
private Process _process;
|
||||
private bool _disposed = false;
|
||||
private bool _disposed;
|
||||
|
||||
public EncodedRecorder(
|
||||
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
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ using MediaBrowser.Model.Dto;
|
|||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
public interface IRecorder
|
||||
public interface IRecorder : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Records the specified media source.
|
||||
|
|
|
@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
IsMovie = IsMovie(details),
|
||||
Etag = programInfo.Md5,
|
||||
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;
|
||||
|
@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
return null;
|
||||
}
|
||||
|
||||
if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (uri.Contains("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
|
|
|
@ -84,38 +84,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
|
||||
|
||||
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);
|
||||
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
|
||||
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (stream.ConfigureAwait(false))
|
||||
{
|
||||
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await using var stream = AsyncFile.OpenRead(info.Path);
|
||||
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
|
||||
var stream = AsyncFile.OpenRead(info.Path);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -1101,7 +1101,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
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);
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
return list;
|
||||
}
|
||||
|
||||
protected virtual List<TunerHostInfo> GetTunerHosts()
|
||||
protected virtual IList<TunerHostInfo> GetTunerHosts()
|
||||
{
|
||||
return GetConfiguration().TunerHosts
|
||||
.Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -96,8 +96,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
|
||||
await using var writeStream = AsyncFile.OpenWrite(channelCacheFile);
|
||||
await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
var writeStream = AsyncFile.OpenWrite(channelCacheFile);
|
||||
await using (writeStream.ConfigureAwait(false))
|
||||
{
|
||||
await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
@ -112,10 +115,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
{
|
||||
try
|
||||
{
|
||||
await using var readStream = AsyncFile.OpenRead(channelCacheFile);
|
||||
var channels = await JsonSerializer.DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
list.AddRange(channels);
|
||||
var readStream = AsyncFile.OpenRead(channelCacheFile);
|
||||
await using (readStream.ConfigureAwait(false))
|
||||
{
|
||||
var channels = await JsonSerializer
|
||||
.DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
list.AddRange(channels);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
|
@ -159,9 +166,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
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);
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
{
|
||||
var model = ModelNumber ?? string.Empty;
|
||||
|
||||
if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
if (model.Contains("hdtc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -527,7 +527,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
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;
|
||||
|
||||
|
|
|
@ -112,6 +112,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
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;
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
.ConfigureAwait(false);
|
||||
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)
|
||||
|
|
|
@ -83,14 +83,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath);
|
||||
using (response)
|
||||
{
|
||||
await using 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 StreamHelper.CopyToAsync(
|
||||
stream,
|
||||
fileStream,
|
||||
IODefaults.CopyToBufferSize,
|
||||
() => Resolve(openTaskCompletionSource),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (stream.ConfigureAwait(false))
|
||||
{
|
||||
var fileStream = new FileStream(
|
||||
TempFilePath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
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)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#pragma warning disable CA1711, CS1591
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -9,7 +10,7 @@ using MediaBrowser.Model.Dto;
|
|||
|
||||
namespace MediaBrowser.Controller.Library
|
||||
{
|
||||
public interface ILiveStream
|
||||
public interface ILiveStream : IDisposable
|
||||
{
|
||||
int ConsumerCount { get; set; }
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <param name="currentLiveStreams">The current live streams.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||
/// <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>
|
||||
/// Gets the channel stream media sources.
|
||||
|
|
Loading…
Reference in New Issue
Block a user