From 1603d1928e888abeccc6a5fab2e1f13aa65eea6c Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 10 Sep 2021 09:29:14 +0200 Subject: Kill ProgressiveFileCopier and seek to end for ongoing livetv --- .../LiveTv/EmbyTV/DirectRecorder.cs | 16 ++- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 7 +- .../LiveTv/TunerHosts/LiveStream.cs | 117 ++++----------------- .../LiveTv/TunerHosts/SharedHttpStream.cs | 49 +++------ 4 files changed, 50 insertions(+), 139 deletions(-) (limited to 'Emby.Server.Implementations/LiveTv') diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index c5a9a92ec2..fdefa2679a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -5,6 +5,7 @@ using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Api.Helpers; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; @@ -50,16 +51,23 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { onStarted(); - _logger.LogInformation("Copying recording stream to file {0}", targetFile); + _logger.LogInformation("Copying recording to file {FilePath}", targetFile); // The media source is infinite so we need to handle stopping ourselves using var durationToken = new CancellationTokenSource(duration); using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); - - await directStreamProvider.CopyToAsync(output, cancellationTokenSource.Token).ConfigureAwait(false); + var linkedCancellationToken = cancellationTokenSource.Token; + + await using var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream(), null, null); + await _streamHelper.CopyToAsync( + fileStream, + output, + IODefaults.CopyToBufferSize, + 1000, + linkedCancellationToken).ConfigureAwait(false); } - _logger.LogInformation("Recording completed to file {0}", targetFile); + _logger.LogInformation("Recording completed: {FilePath}", targetFile); } private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 58e0c7448a..a8f761fdeb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -156,11 +156,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun await taskCompletionSource.Task.ConfigureAwait(false); } - public string GetFilePath() - { - return TempFilePath; - } - private async Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { using (udpClient) @@ -184,7 +179,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun EnableStreamSharing = false; } - await DeleteTempFiles(new List { TempFilePath }).ConfigureAwait(false); + await DeleteTempFiles(TempFilePath).ConfigureAwait(false); } private async Task CopyTo(UdpClient udpClient, string file, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 2c21a4a893..4f9d343276 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -3,10 +3,8 @@ #pragma warning disable CS1591 using System; -using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -97,6 +95,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.CompletedTask; } + public Stream GetStream(bool seekNearEnd = true) + { + var stream = GetInputStream(TempFilePath, AsyncFile.UseAsyncIO); + bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10; + if (seekFile) + { + TrySeek(stream, -20000); + } + + return stream; + } + protected FileStream GetInputStream(string path, bool allowAsyncFileRead) => new FileStream( path, @@ -106,112 +116,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts IODefaults.FileStreamBufferSize, allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan); - public Task DeleteTempFiles() - { - return DeleteTempFiles(GetStreamFilePaths()); - } - - protected async Task DeleteTempFiles(IEnumerable paths, int retryCount = 0) + protected async Task DeleteTempFiles(string path, int retryCount = 0) { if (retryCount == 0) { - Logger.LogInformation("Deleting temp files {0}", paths); - } - - var failedFiles = new List(); - - foreach (var path in paths) - { - if (!File.Exists(path)) - { - continue; - } - - try - { - FileSystem.DeleteFile(path); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error deleting file {path}", path); - failedFiles.Add(path); - } - } - - if (failedFiles.Count > 0 && retryCount <= 40) - { - await Task.Delay(500).ConfigureAwait(false); - await DeleteTempFiles(failedFiles, retryCount + 1).ConfigureAwait(false); + Logger.LogInformation("Deleting temp file {FilePath}", path); } - } - protected virtual List GetStreamFilePaths() - { - return new List { TempFilePath }; - } - - public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) - { - using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token); - cancellationToken = linkedCancellationTokenSource.Token; - - bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10; - - var nextFileInfo = GetNextFile(null); - var nextFile = nextFileInfo.file; - var isLastFile = nextFileInfo.isLastFile; - - var allowAsync = AsyncFile.UseAsyncIO; - while (!string.IsNullOrEmpty(nextFile)) - { - var emptyReadLimit = isLastFile ? EmptyReadLimit : 1; - - await CopyFile(nextFile, seekFile, emptyReadLimit, allowAsync, stream, cancellationToken).ConfigureAwait(false); - - seekFile = false; - nextFileInfo = GetNextFile(nextFile); - nextFile = nextFileInfo.file; - isLastFile = nextFileInfo.isLastFile; - } - - Logger.LogInformation("Live Stream ended."); - } - - private (string file, bool isLastFile) GetNextFile(string currentFile) - { - var files = GetStreamFilePaths(); - - if (string.IsNullOrEmpty(currentFile)) + try { - return (files[^1], true); + FileSystem.DeleteFile(path); } - - var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1; - - var isLastFile = nextIndex == files.Count - 1; - - return (files.ElementAtOrDefault(nextIndex), isLastFile); - } - - private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken) - { - using (var inputStream = GetInputStream(path, allowAsync)) + catch (Exception ex) { - if (seekFile) + Logger.LogError(ex, "Error deleting file {FilePath}", path); + if (retryCount <= 40) { - TrySeek(inputStream, -20000); + await Task.Delay(500).ConfigureAwait(false); + await DeleteTempFiles(path, retryCount + 1).ConfigureAwait(false); } - - await StreamHelper.CopyToAsync( - inputStream, - stream, - IODefaults.CopyToBufferSize, - emptyReadLimit, - cancellationToken).ConfigureAwait(false); } } - private void TrySeek(FileStream stream, long offset) + private void TrySeek(Stream stream, long offset) { if (!stream.CanSeek) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 8629938777..84a878e517 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -3,7 +3,6 @@ #pragma warning disable CS1591 using System; -using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net.Http; @@ -55,39 +54,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath)); var typeName = GetType().Name; - Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url); + Logger.LogInformation("Opening {StreamType} Live stream from {Url}", typeName, url); // Response stream is disposed manually. var response = await _httpClientFactory.CreateClient(NamedClient.Default) .GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None) .ConfigureAwait(false); - var extension = "ts"; - var requiresRemux = false; - var contentType = response.Content.Headers.ContentType?.ToString() ?? string.Empty; - if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1) - { - requiresRemux = true; - } - else if (contentType.IndexOf("mp4", StringComparison.OrdinalIgnoreCase) != -1 || - contentType.IndexOf("dash", StringComparison.OrdinalIgnoreCase) != -1 || - contentType.IndexOf("mpegURL", StringComparison.OrdinalIgnoreCase) != -1 || - contentType.IndexOf("text/", StringComparison.OrdinalIgnoreCase) != -1) - { - requiresRemux = true; - } - - // Close the stream without any sharing features - if (requiresRemux) + if (contentType.Contains("matroska", StringComparison.OrdinalIgnoreCase) + || contentType.Contains("mp4", StringComparison.OrdinalIgnoreCase) + || contentType.Contains("dash", StringComparison.OrdinalIgnoreCase) + || contentType.Contains("mpegURL", StringComparison.OrdinalIgnoreCase) + || contentType.Contains("text/", StringComparison.OrdinalIgnoreCase)) { - using (response) - { - return; - } + // Close the stream without any sharing features + response.Dispose(); + return; } - SetTempFilePath(extension); + SetTempFilePath("ts"); var taskCompletionSource = new TaskCompletionSource(); @@ -117,16 +103,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (!taskCompletionSource.Task.Result) { - Logger.LogWarning("Zero bytes copied from stream {0} to {1} but no exception raised", GetType().Name, TempFilePath); + Logger.LogWarning("Zero bytes copied from stream {StreamType} to {FilePath} but no exception raised", GetType().Name, TempFilePath); throw new EndOfStreamException(string.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name)); } } - public string GetFilePath() - { - return TempFilePath; - } - private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { return Task.Run( @@ -134,7 +115,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { try { - Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); + Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath); using var message = 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, AsyncFile.UseAsyncIO); @@ -147,19 +128,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } catch (OperationCanceledException ex) { - Logger.LogInformation("Copying of {0} to {1} was canceled", GetType().Name, TempFilePath); + Logger.LogInformation("Copying of {StreamType} to {FilePath} was canceled", GetType().Name, TempFilePath); openTaskCompletionSource.TrySetException(ex); } catch (Exception ex) { - Logger.LogError(ex, "Error copying live stream {0} to {1}.", GetType().Name, TempFilePath); + Logger.LogError(ex, "Error copying live stream {StreamType} to {FilePath}", GetType().Name, TempFilePath); openTaskCompletionSource.TrySetException(ex); } openTaskCompletionSource.TrySetResult(false); EnableStreamSharing = false; - await DeleteTempFiles(new List { TempFilePath }).ConfigureAwait(false); + await DeleteTempFiles(TempFilePath).ConfigureAwait(false); }, CancellationToken.None); } -- cgit v1.2.3 From f3573b061c4d9eb869316ce3de320fd8803aeef8 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 10 Sep 2021 09:56:48 +0200 Subject: Remove the unused arg --- Emby.Server.Implementations/Library/ExclusiveLiveStream.cs | 2 +- Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs | 2 +- MediaBrowser.Controller/Library/IDirectStreamProvider.cs | 5 ++--- MediaBrowser.Controller/Library/ILiveStream.cs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations/LiveTv') diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 9ba9f2115e..868071a992 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.Library return _closeFn(); } - public Stream GetStream(bool seekNearEnd) + public Stream GetStream() { throw new NotSupportedException(); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 4f9d343276..817b1f8046 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.CompletedTask; } - public Stream GetStream(bool seekNearEnd = true) + public Stream GetStream() { var stream = GetInputStream(TempFilePath, AsyncFile.UseAsyncIO); bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10; diff --git a/MediaBrowser.Controller/Library/IDirectStreamProvider.cs b/MediaBrowser.Controller/Library/IDirectStreamProvider.cs index 2939668f7d..96f8b7eba9 100644 --- a/MediaBrowser.Controller/Library/IDirectStreamProvider.cs +++ b/MediaBrowser.Controller/Library/IDirectStreamProvider.cs @@ -11,10 +11,9 @@ namespace MediaBrowser.Controller.Library public interface IDirectStreamProvider { /// - /// Gets the live stream, optionally seeks to the end of the file first. + /// Gets the live stream, shared streams seek to the end of the file first. /// - /// A value indicating whether to seek to the end of the file. /// The stream. - Stream GetStream(bool seekNearEnd = true); + Stream GetStream(); } } diff --git a/MediaBrowser.Controller/Library/ILiveStream.cs b/MediaBrowser.Controller/Library/ILiveStream.cs index a29a91fe9c..4c44a17fdd 100644 --- a/MediaBrowser.Controller/Library/ILiveStream.cs +++ b/MediaBrowser.Controller/Library/ILiveStream.cs @@ -27,6 +27,6 @@ namespace MediaBrowser.Controller.Library Task Close(); - Stream GetStream(bool seekNearEnd = true); + Stream GetStream(); } } -- cgit v1.2.3 From 1a5a74d2a95506249cf071c659e3c6cf01f28f78 Mon Sep 17 00:00:00 2001 From: cvium Date: Fri, 10 Sep 2021 10:03:42 +0200 Subject: Remove more unused args --- Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs | 2 +- Jellyfin.Api/Controllers/LiveTvController.cs | 2 +- Jellyfin.Api/Controllers/VideosController.cs | 2 +- Jellyfin.Api/Helpers/AudioHelper.cs | 2 +- Jellyfin.Api/Helpers/ProgressiveFileStream.cs | 8 +++----- 5 files changed, 7 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations/LiveTv') diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index fdefa2679a..f6e0111b6d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -58,7 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); var linkedCancellationToken = cancellationTokenSource.Token; - await using var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream(), null, null); + await using var fileStream = new ProgressiveFileStream(directStreamProvider.GetStream()); await _streamHelper.CopyToAsync( fileStream, output, diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index c3856b882e..3f68b267f3 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1207,7 +1207,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream(), null, _transcodingJobHelper); + var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container)); } diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 0af10ceefa..d7d48ba3e7 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -454,7 +454,7 @@ namespace Jellyfin.Api.Controllers StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfo(streamingRequest.LiveStreamId); - var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream(), null, _transcodingJobHelper); + var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); // TODO (moved from MediaBrowser.Api): Don't hardcode contentType return File(liveStream, MimeTypes.GetMimeType("file.ts")!); } diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index 19da180e53..06f889f085 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -121,7 +121,7 @@ namespace Jellyfin.Api.Helpers StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfo(streamingRequest.LiveStreamId); - var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream(), null, _transcodingJobHelper); + var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); // TODO (moved from MediaBrowser.Api): Don't hardcode contentType return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file.ts")); } diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs index 9939734179..c570183514 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileStream.cs @@ -51,13 +51,11 @@ namespace Jellyfin.Api.Helpers /// Initializes a new instance of the class. /// /// The stream to progressively copy. - /// The transcoding job information. - /// The transcoding job helper. /// The timeout duration in milliseconds. - public ProgressiveFileStream(Stream stream, TranscodingJobDto? job, TranscodingJobHelper? transcodingJobHelper, int timeoutMs = 30000) + public ProgressiveFileStream(Stream stream, int timeoutMs = 30000) { - _job = job; - _transcodingJobHelper = transcodingJobHelper; + _job = null; + _transcodingJobHelper = null; _timeoutMs = timeoutMs; _allowAsyncFileRead = AsyncFile.UseAsyncIO; _stream = stream; -- cgit v1.2.3