From 2ca2a217377281d06b3d35486c1f21d084d667c5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 1 Jun 2017 00:51:43 -0400 Subject: [PATCH] update live stream buffer --- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 4 +- .../HdHomerun/HdHomerunHttpStream.cs | 42 ++++--- .../HdHomerun/HdHomerunUdpStream.cs | 103 ++++++++++++------ MediaBrowser.Controller/IO/StreamHelper.cs | 20 ++++ MediaBrowser.Controller/LiveTv/LiveStream.cs | 4 +- .../MediaBrowser.Controller.csproj | 1 + 6 files changed, 119 insertions(+), 55 deletions(-) create mode 100644 MediaBrowser.Controller/IO/StreamHelper.cs diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 504f9a6ee6..752f55651e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -423,7 +423,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun IsInfiniteStream = true, IgnoreDts = true, //IgnoreIndex = true, - ReadAtNativeFramerate = true + //ReadAtNativeFramerate = true }; mediaSource.InferTotalBitrate(); @@ -513,7 +513,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet var enableHttpStream = _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX || _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.BSD; - enableHttpStream = true; + //enableHttpStream = true; if (enableHttpStream) { mediaSource.Protocol = MediaProtocol.Http; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs index 5db842dec7..a680f21836 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs @@ -12,6 +12,8 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; +using System.Globalization; +using MediaBrowser.Controller.IO; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -102,12 +104,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _logger.Info("Beginning multicastStream.CopyUntilCancelled"); FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); - using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous)) + using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) { ResolveAfterDelay(3000, openTaskCompletionSource); //await response.Content.CopyToAsync(fileStream, 81920, cancellationToken).ConfigureAwait(false); - await AsyncStreamCopier.CopyStream(response.Content, fileStream, 81920, 4, cancellationToken).ConfigureAwait(false); + StreamHelper.CopyTo(response.Content, fileStream, 81920, cancellationToken); + + //await AsyncStreamCopier.CopyStream(response.Content, fileStream, 81920, 4, cancellationToken).ConfigureAwait(false); } } } @@ -147,43 +151,51 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - return CopyFileTo(_tempFilePath, false, stream, cancellationToken); + return CopyFileTo(_tempFilePath, stream, cancellationToken); } - protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken) + protected async Task CopyFileTo(string path, Stream outputStream, CancellationToken cancellationToken) { - var eofCount = 0; - - long startPosition = -25000; + long startPosition = -20000; if (startPosition < 0) { var length = FileSystem.GetFileInfo(path).Length; startPosition = Math.Max(length - startPosition, 0); } - using (var inputStream = GetInputStream(path, startPosition, true)) + _logger.Info("Live stream starting position is {0} bytes", startPosition.ToString(CultureInfo.InvariantCulture)); + + var allowAsync = Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + + using (var inputStream = GetInputStream(path, startPosition, allowAsync)) { if (startPosition > 0) { inputStream.Position = startPosition; } - while (eofCount < 20 || !allowEndOfFile) + while (!cancellationToken.IsCancellationRequested) { - var bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 4, cancellationToken).ConfigureAwait(false); + long bytesRead; + + if (allowAsync) + { + bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 2, cancellationToken).ConfigureAwait(false); + } + else + { + StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); + bytesRead = 1; + } //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); if (bytesRead == 0) { - eofCount++; await Task.Delay(100, cancellationToken).ConfigureAwait(false); } - else - { - eofCount = 0; - } } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 2989177c0f..e98c5285da 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -16,6 +16,8 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; +using System.Globalization; +using MediaBrowser.Controller.IO; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -122,9 +124,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (!cancellationToken.IsCancellationRequested) { FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); - using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous)) + using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) { - await CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken).ConfigureAwait(false); + ResolveAfterDelay(3000, openTaskCompletionSource); + + CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken); } } } @@ -168,78 +172,107 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - return CopyFileTo(_tempFilePath, false, stream, cancellationToken); + return CopyFileTo(_tempFilePath, stream, cancellationToken); } - protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken) + protected async Task CopyFileTo(string path, Stream outputStream, CancellationToken cancellationToken) { - var eofCount = 0; - - long startPosition = -25000; + long startPosition = -20000; if (startPosition < 0) { var length = FileSystem.GetFileInfo(path).Length; startPosition = Math.Max(length - startPosition, 0); } - using (var inputStream = GetInputStream(path, startPosition, true)) + _logger.Info("Live stream starting position is {0} bytes", startPosition.ToString(CultureInfo.InvariantCulture)); + + var allowAsync = Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + + using (var inputStream = GetInputStream(path, startPosition, allowAsync)) { if (startPosition > 0) { inputStream.Position = startPosition; } - while (eofCount < 20 || !allowEndOfFile) + while (!cancellationToken.IsCancellationRequested) { - var bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 4, cancellationToken).ConfigureAwait(false); + long bytesRead; + + if (allowAsync) + { + bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 2, cancellationToken).ConfigureAwait(false); + } + else + { + StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); + bytesRead = 1; + } //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); if (bytesRead == 0) { - eofCount++; await Task.Delay(100, cancellationToken).ConfigureAwait(false); } - else - { - eofCount = 0; - } } } } - private static int RtpHeaderBytes = 12; - private Task CopyTo(ISocket udpClient, Stream outputStream, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) + private void ResolveAfterDelay(int delayMs, TaskCompletionSource openTaskCompletionSource) { - return CopyStream(_socketFactory.CreateNetworkStream(udpClient, false), outputStream, 81920, 4, openTaskCompletionSource, cancellationToken); + Task.Run(async () => + { + await Task.Delay(delayMs).ConfigureAwait(false); + openTaskCompletionSource.TrySetResult(true); + }); } - private Task CopyStream(Stream source, Stream target, int bufferSize, int bufferCount, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) + private static int RtpHeaderBytes = 12; + private void CopyTo(ISocket udpClient, Stream target, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { - var copier = new AsyncStreamCopier(source, target, 0, cancellationToken, false, bufferSize, bufferCount); - copier.IndividualReadOffset = RtpHeaderBytes; + var source = _socketFactory.CreateNetworkStream(udpClient, false); + var bufferSize = 81920; - var taskCompletion = new TaskCompletionSource(); - - copier.TaskCompletionSource = taskCompletion; + byte[] buffer = new byte[bufferSize]; + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); - var result = copier.BeginCopy(StreamCopyCallback, copier); + read -= RtpHeaderBytes; - if (openTaskCompletionSource != null) - { - Resolve(openTaskCompletionSource); - openTaskCompletionSource = null; + if (read > 0) + { + target.Write(buffer, RtpHeaderBytes, read); + } } - if (result.CompletedSynchronously) - { - StreamCopyCallback(result); - } + //var copier = new AsyncStreamCopier(source, target, 0, cancellationToken, false, bufferSize, bufferCount); + //copier.IndividualReadOffset = RtpHeaderBytes; + + //var taskCompletion = new TaskCompletionSource(); + + //copier.TaskCompletionSource = taskCompletion; + + //var result = copier.BeginCopy(StreamCopyCallback, copier); + + //if (openTaskCompletionSource != null) + //{ + // Resolve(openTaskCompletionSource); + // openTaskCompletionSource = null; + //} + + //if (result.CompletedSynchronously) + //{ + // StreamCopyCallback(result); + //} - cancellationToken.Register(() => taskCompletion.TrySetCanceled()); + //cancellationToken.Register(() => taskCompletion.TrySetCanceled()); - return taskCompletion.Task; + //return taskCompletion.Task; } private void StreamCopyCallback(IAsyncResult result) diff --git a/MediaBrowser.Controller/IO/StreamHelper.cs b/MediaBrowser.Controller/IO/StreamHelper.cs new file mode 100644 index 0000000000..168d4b8c6b --- /dev/null +++ b/MediaBrowser.Controller/IO/StreamHelper.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Threading; + +namespace MediaBrowser.Controller.IO +{ + public static class StreamHelper + { + public static void CopyTo(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken) + { + byte[] buffer = new byte[bufferSize]; + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + destination.Write(buffer, 0, read); + } + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveStream.cs b/MediaBrowser.Controller/LiveTv/LiveStream.cs index 912fed23c2..b90d0e3d20 100644 --- a/MediaBrowser.Controller/LiveTv/LiveStream.cs +++ b/MediaBrowser.Controller/LiveTv/LiveStream.cs @@ -53,9 +53,7 @@ namespace MediaBrowser.Controller.LiveTv protected Stream GetInputStream(string path, long startPosition, bool allowAsyncFileRead) { - var fileOpenOptions = startPosition > 0 - ? FileOpenOptions.RandomAccess - : FileOpenOptions.SequentialScan; + var fileOpenOptions = FileOpenOptions.SequentialScan; if (allowAsyncFileRead) { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index d8b81027c5..e15b58e77a 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -134,6 +134,7 @@ +