From 2f758676d0908f07dafc3ec5434bbaf4cf82529c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 14 Nov 2017 02:41:21 -0500 Subject: support sharing m3u tuner streams --- .../Emby.Server.Implementations.csproj | 2 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 +- .../TunerHosts/HdHomerun/HdHomerunHttpStream.cs | 119 ----------------- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 1 + .../LiveTv/TunerHosts/LiveStream.cs | 13 +- .../LiveTv/TunerHosts/M3UTunerHost.cs | 10 +- .../LiveTv/TunerHosts/SharedHttpStream.cs | 145 +++++++++++++++++++++ MediaBrowser.Api/Library/LibraryService.cs | 15 ++- MediaBrowser.Common/Net/HttpRequestOptions.cs | 2 + MediaBrowser.Controller/Entities/Folder.cs | 10 ++ SharedVersion.cs | 2 +- 11 files changed, 190 insertions(+), 131 deletions(-) delete mode 100644 Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs create mode 100644 Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 6b2a005f9..7ccf54d20 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -404,11 +404,11 @@ - + diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 8cfe9de39..08eab9a35 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -618,7 +618,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } mediaSource.Path = httpUrl; - return new HdHomerunHttpStream(mediaSource, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment); + return new SharedHttpStream(mediaSource, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment); } return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs deleted file mode 100644 index ddbbda737..000000000 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Library; -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 -{ - public class HdHomerunHttpStream : LiveStream, IDirectStreamProvider - { - private readonly IHttpClient _httpClient; - private readonly IServerApplicationHost _appHost; - - public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment) - : base(mediaSource, environment, fileSystem, logger, appPaths) - { - _httpClient = httpClient; - _appHost = appHost; - OriginalStreamId = originalStreamId; - } - - public override async Task Open(CancellationToken openCancellationToken) - { - LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); - - var mediaSource = OriginalMediaSource; - - var url = mediaSource.Path; - - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath)); - - Logger.Info("Opening HDHR Live stream from {0}", url); - - var response = await _httpClient.SendAsync(new HttpRequestOptions - { - Url = url, - CancellationToken = CancellationToken.None, - BufferContent = false, - - // Increase a little bit - TimeoutMs = 30000, - - EnableHttpCompression = false - - }, "GET").ConfigureAwait(false); - - Logger.Info("Opened HDHR stream from {0}", url); - - var taskCompletionSource = new TaskCompletionSource(); - StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); - - //OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.Path = tempFile; - //OpenedMediaSource.ReadAtNativeFramerate = true; - - OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; - OpenedMediaSource.Protocol = MediaProtocol.Http; - - //OpenedMediaSource.Path = _tempFilePath; - //OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.SupportsDirectPlay = false; - //OpenedMediaSource.SupportsDirectStream = true; - //OpenedMediaSource.SupportsTranscoding = true; - await taskCompletionSource.Task.ConfigureAwait(false); - } - - protected override void CloseInternal() - { - LiveStreamCancellationTokenSource.Cancel(); - } - - private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) - { - return Task.Run(async () => - { - try - { - using (response) - { - using (var stream = response.Content) - { - Logger.Info("Beginning HdHomerunHttpStream stream to file"); - - using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) - { - StreamHelper.CopyTo(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken); - } - } - } - } - catch (OperationCanceledException) - { - } - catch (Exception ex) - { - Logger.ErrorException("Error copying live stream.", ex); - } - EnableStreamSharing = false; - await DeleteTempFile(TempFilePath).ConfigureAwait(false); - }); - } - - private void Resolve(TaskCompletionSource openTaskCompletionSource) - { - openTaskCompletionSource.TrySetResult(true); - } - } -} diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 8b46b78be..a32930080 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -32,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun OriginalStreamId = originalStreamId; _channelCommands = channelCommands; _numTuners = numTuners; + EnableStreamSharing = true; } public override async Task Open(CancellationToken openCancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index cead1def0..bec92716b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -29,8 +29,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public List SharedStreamIds { get; private set; } protected readonly IEnvironmentInfo Environment; protected readonly IFileSystem FileSystem; + protected readonly IServerApplicationPaths AppPaths; - protected readonly string TempFilePath; + protected string TempFilePath; protected readonly ILogger Logger; protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource(); @@ -44,7 +45,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts EnableStreamSharing = true; SharedStreamIds = new List(); UniqueId = Guid.NewGuid().ToString("N"); - TempFilePath = Path.Combine(appPaths.GetTranscodingTempPath(), UniqueId + ".ts"); + + AppPaths = appPaths; + + SetTempFilePath("ts"); + } + + protected void SetTempFilePath(string extension) + { + TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension); } public virtual Task Open(CancellationToken openCancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 9fc6687d1..e41fb7fbe 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -79,8 +79,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false); - var liveStream = new LiveStream(sources.First(), _environment, FileSystem, Logger, Config.ApplicationPaths); - return liveStream; + var mediaSource = sources.First(); + + if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping) + { + return new SharedHttpStream(mediaSource, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment); + } + + return new LiveStream(mediaSource, _environment, FileSystem, Logger, Config.ApplicationPaths); } public async Task Validate(TunerHostInfo info) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs new file mode 100644 index 000000000..a33b0945b --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -0,0 +1,145 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.IO; +using MediaBrowser.Model.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Library; +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 +{ + public class SharedHttpStream : LiveStream, IDirectStreamProvider + { + private readonly IHttpClient _httpClient; + private readonly IServerApplicationHost _appHost; + + public SharedHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment) + : base(mediaSource, environment, fileSystem, logger, appPaths) + { + _httpClient = httpClient; + _appHost = appHost; + OriginalStreamId = originalStreamId; + EnableStreamSharing = true; + } + + public override async Task Open(CancellationToken openCancellationToken) + { + LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); + + var mediaSource = OriginalMediaSource; + + var url = mediaSource.Path; + + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath)); + + var typeName = GetType().Name; + Logger.Info("Opening " + typeName + " Live stream from {0}", url); + + var response = await _httpClient.SendAsync(new HttpRequestOptions + { + Url = url, + CancellationToken = CancellationToken.None, + BufferContent = false, + + // Increase a little bit + TimeoutMs = 30000, + + EnableHttpCompression = false, + + LogResponse = true, + LogResponseHeaders = true + + }, "GET").ConfigureAwait(false); + + var extension = "ts"; + var requiresRemux = false; + + var contentType = response.ContentType ?? string.Empty; + if (contentType.IndexOf("mp4", StringComparison.OrdinalIgnoreCase) != -1 || + contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1 || + contentType.IndexOf("dash", StringComparison.OrdinalIgnoreCase) != -1 || + contentType.IndexOf("mpegURL", StringComparison.OrdinalIgnoreCase) != -1) + { + requiresRemux = true; + } + + // Close the stream without any sharing features + if (requiresRemux) + { + using (response) + { + return; + } + } + + SetTempFilePath(extension); + + var taskCompletionSource = new TaskCompletionSource(); + StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); + + //OpenedMediaSource.Protocol = MediaProtocol.File; + //OpenedMediaSource.Path = tempFile; + //OpenedMediaSource.ReadAtNativeFramerate = true; + + OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; + OpenedMediaSource.Protocol = MediaProtocol.Http; + + //OpenedMediaSource.Path = _tempFilePath; + //OpenedMediaSource.Protocol = MediaProtocol.File; + //OpenedMediaSource.SupportsDirectPlay = false; + //OpenedMediaSource.SupportsDirectStream = true; + //OpenedMediaSource.SupportsTranscoding = true; + await taskCompletionSource.Task.ConfigureAwait(false); + } + + protected override void CloseInternal() + { + LiveStreamCancellationTokenSource.Cancel(); + } + + private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) + { + return Task.Run(async () => + { + try + { + using (response) + { + using (var stream = response.Content) + { + Logger.Info("Beginning {0} stream to {1}", GetType().Name, TempFilePath); + + using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) + { + StreamHelper.CopyTo(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken); + } + } + } + } + catch (OperationCanceledException) + { + } + catch (Exception ex) + { + Logger.ErrorException("Error copying live stream.", ex); + } + EnableStreamSharing = false; + await DeleteTempFile(TempFilePath).ConfigureAwait(false); + }); + } + + private void Resolve(TaskCompletionSource openTaskCompletionSource) + { + openTaskCompletionSource.TrySetResult(true); + } + } +} diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 6152ea20b..a036a00a6 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -335,7 +335,8 @@ namespace MediaBrowser.Api.Library Fields = request.Fields, Id = request.Id, Limit = request.Limit, - UserId = request.UserId + UserId = request.UserId, + ImageTypeLimit = request.ImageTypeLimit }); } if (item is MusicAlbum) @@ -350,7 +351,8 @@ namespace MediaBrowser.Api.Library Id = request.Id, Limit = request.Limit, UserId = request.UserId, - ExcludeArtistIds = request.ExcludeArtistIds + ExcludeArtistIds = request.ExcludeArtistIds, + ImageTypeLimit = request.ImageTypeLimit }); } if (item is MusicArtist) @@ -364,7 +366,8 @@ namespace MediaBrowser.Api.Library Fields = request.Fields, Id = request.Id, Limit = request.Limit, - UserId = request.UserId + UserId = request.UserId, + ImageTypeLimit = request.ImageTypeLimit }); } @@ -381,7 +384,8 @@ namespace MediaBrowser.Api.Library Fields = request.Fields, Id = request.Id, Limit = request.Limit, - UserId = request.UserId + UserId = request.UserId, + ImageTypeLimit = request.ImageTypeLimit }); } @@ -396,7 +400,8 @@ namespace MediaBrowser.Api.Library Fields = request.Fields, Id = request.Id, Limit = request.Limit, - UserId = request.UserId + UserId = request.UserId, + ImageTypeLimit = request.ImageTypeLimit }); } diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 0a279fa9c..8f0b155f3 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -93,6 +93,8 @@ namespace MediaBrowser.Common.Net public bool LogRequest { get; set; } public bool LogRequestAsDebug { get; set; } public bool LogErrors { get; set; } + public bool LogResponse { get; set; } + public bool LogResponseHeaders { get; set; } public bool LogErrorResponseBody { get; set; } public bool EnableKeepAlive { get; set; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index fb283067f..504d03a27 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1421,6 +1421,16 @@ namespace MediaBrowser.Controller.Entities // Sweep through recursively and update status foreach (var item in itemsResult) { + if (item.IsVirtualItem) + { + // The querying doesn't support virtual unaired + var episode = item as Episode; + if (episode != null && episode.IsUnaired) + { + continue; + } + } + item.MarkPlayed(user, datePlayed, resetPosition); } } diff --git a/SharedVersion.cs b/SharedVersion.cs index c89077695..812369b17 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.36.8")] +[assembly: AssemblyVersion("3.2.36.9")] -- cgit v1.2.3