improve live tv direct play

This commit is contained in:
Luke Pulverenti 2017-10-14 02:52:56 -04:00
parent 2d63bdea94
commit 164e7dc896
10 changed files with 110 additions and 58 deletions

View file

@ -792,6 +792,11 @@ namespace Emby.Server.Implementations
protected abstract IConnectManager CreateConnectManager(); protected abstract IConnectManager CreateConnectManager();
protected abstract ISyncManager CreateSyncManager(); protected abstract ISyncManager CreateSyncManager();
protected virtual IHttpClient CreateHttpClient()
{
return new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory, GetDefaultUserAgent);
}
/// <summary> /// <summary>
/// Registers resources that classes will depend on /// Registers resources that classes will depend on
@ -814,7 +819,7 @@ namespace Emby.Server.Implementations
RegisterSingleInstance(FileSystemManager); RegisterSingleInstance(FileSystemManager);
HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory, GetDefaultUserAgent); HttpClient = CreateHttpClient();
RegisterSingleInstance(HttpClient); RegisterSingleInstance(HttpClient);
RegisterSingleInstance(NetworkManager); RegisterSingleInstance(NetworkManager);
@ -1118,7 +1123,7 @@ namespace Emby.Server.Implementations
IsoManager.AddParts(list); IsoManager.AddParts(list);
} }
private string GetDefaultUserAgent() protected string GetDefaultUserAgent()
{ {
var name = FormatAttribute(Name); var name = FormatAttribute(Name);

View file

@ -72,10 +72,18 @@ namespace Emby.Server.Implementations.Devices
private void MigrateDevices() private void MigrateDevices()
{ {
var files = FileSystem List<string> files;
.GetFilePaths(GetDevicesPath(), true) try
.Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) {
.ToList(); files = FileSystem
.GetFilePaths(GetDevicesPath(), true)
.Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase))
.ToList();
}
catch (IOException)
{
return;
}
foreach (var file in files) foreach (var file in files)
{ {

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Net.Http;
namespace Emby.Server.Implementations.HttpClientManager namespace Emby.Server.Implementations.HttpClientManager
{ {
@ -12,5 +13,6 @@ namespace Emby.Server.Implementations.HttpClientManager
/// </summary> /// </summary>
/// <value>The last timeout.</value> /// <value>The last timeout.</value>
public DateTime LastTimeout { get; set; } public DateTime LastTimeout { get; set; }
public HttpClient HttpClient { get; set; }
} }
} }

View file

@ -22,7 +22,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment) public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment)
@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
protected override Task OpenInternal(CancellationToken openCancellationToken) protected override Task OpenInternal(CancellationToken openCancellationToken)
{ {
_liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
var mediaSource = OriginalMediaSource; var mediaSource = OriginalMediaSource;
@ -45,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var taskCompletionSource = new TaskCompletionSource<bool>(); var taskCompletionSource = new TaskCompletionSource<bool>();
StartStreaming(url, taskCompletionSource, _liveStreamCancellationTokenSource.Token); StartStreaming(url, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile; //OpenedMediaSource.Path = tempFile;
@ -65,12 +64,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
//await Task.Delay(5000).ConfigureAwait(false); //await Task.Delay(5000).ConfigureAwait(false);
} }
public override Task Close() public override async Task Close()
{ {
Logger.Info("Closing HDHR live stream"); Logger.Info("Closing HDHR live stream");
_liveStreamCancellationTokenSource.Cancel(); LiveStreamCancellationTokenSource.Cancel();
return _liveStreamTaskCompletionSource.Task; await _liveStreamTaskCompletionSource.Task.ConfigureAwait(false);
await DeleteTempFile(TempFilePath).ConfigureAwait(false);
} }
private Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) private Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
@ -112,7 +112,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
_liveStreamTaskCompletionSource.TrySetResult(true); _liveStreamTaskCompletionSource.TrySetResult(true);
await DeleteTempFile(TempFilePath).ConfigureAwait(false);
}); });
} }

View file

@ -26,7 +26,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
private readonly IHdHomerunChannelCommands _channelCommands; private readonly IHdHomerunChannelCommands _channelCommands;
private readonly int _numTuners; private readonly int _numTuners;
@ -45,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
protected override Task OpenInternal(CancellationToken openCancellationToken) protected override Task OpenInternal(CancellationToken openCancellationToken)
{ {
_liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
var mediaSource = OriginalMediaSource; var mediaSource = OriginalMediaSource;
@ -56,7 +55,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var taskCompletionSource = new TaskCompletionSource<bool>(); var taskCompletionSource = new TaskCompletionSource<bool>();
StartStreaming(uri.Host, localPort, taskCompletionSource, _liveStreamCancellationTokenSource.Token); StartStreaming(uri.Host, localPort, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile; //OpenedMediaSource.Path = tempFile;
@ -76,7 +75,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public override Task Close() public override Task Close()
{ {
Logger.Info("Closing HDHR UDP live stream"); Logger.Info("Closing HDHR UDP live stream");
_liveStreamCancellationTokenSource.Cancel(); LiveStreamCancellationTokenSource.Cancel();
return _liveStreamTaskCompletionSource.Task; return _liveStreamTaskCompletionSource.Task;
} }

View file

@ -32,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected readonly string TempFilePath; protected readonly string TempFilePath;
protected readonly ILogger Logger; protected readonly ILogger Logger;
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
public LiveStream(MediaSourceInfo mediaSource, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths) public LiveStream(MediaSourceInfo mediaSource, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
{ {
@ -80,6 +81,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
FileSystem.DeleteFile(path); FileSystem.DeleteFile(path);
return; return;
} }
catch (DirectoryNotFoundException)
{
return;
}
catch (FileNotFoundException)
{
return;
}
catch catch
{ {
@ -96,6 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{ {
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token;
var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
@ -110,16 +121,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private static async Task CopyTo(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) private static async Task CopyTo(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
{ {
byte[] buffer = new byte[bufferSize]; byte[] buffer = new byte[bufferSize];
while (true)
var eofCount = 0;
var emptyReadLimit = 1000;
while (eofCount < emptyReadLimit)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var read = source.Read(buffer, 0, buffer.Length); var bytesRead = source.Read(buffer, 0, buffer.Length);
if (read > 0) if (bytesRead == 0)
{ {
eofCount++;
await Task.Delay(10, cancellationToken).ConfigureAwait(false);
}
else
{
eofCount = 0;
//await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); //await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false);
destination.Write(buffer, 0, read); destination.Write(buffer, 0, bytesRead);
if (onStarted != null) if (onStarted != null)
{ {
@ -127,10 +149,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
onStarted = null; onStarted = null;
} }
} }
else
{
await Task.Delay(10).ConfigureAwait(false);
}
} }
} }

View file

@ -734,7 +734,7 @@ namespace MediaBrowser.Api.LiveTv
outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path);
return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, Logger, _environment, CancellationToken.None) return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, Logger, _environment)
{ {
AllowEndOfFile = false AllowEndOfFile = false
}; };
@ -753,7 +753,7 @@ namespace MediaBrowser.Api.LiveTv
outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container);
return new ProgressiveFileCopier(directStreamProvider, outputHeaders, Logger, _environment, CancellationToken.None) return new ProgressiveFileCopier(directStreamProvider, outputHeaders, Logger, _environment)
{ {
AllowEndOfFile = false AllowEndOfFile = false
}; };

View file

@ -16,7 +16,6 @@ namespace MediaBrowser.Api.LiveTv
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly string _path; private readonly string _path;
private readonly CancellationToken _cancellationToken;
private readonly Dictionary<string, string> _outputHeaders; private readonly Dictionary<string, string> _outputHeaders;
const int StreamCopyToBufferSize = 81920; const int StreamCopyToBufferSize = 81920;
@ -28,22 +27,20 @@ namespace MediaBrowser.Api.LiveTv
private readonly IDirectStreamProvider _directStreamProvider; private readonly IDirectStreamProvider _directStreamProvider;
private readonly IEnvironmentInfo _environment; private readonly IEnvironmentInfo _environment;
public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, ILogger logger, IEnvironmentInfo environment)
{ {
_fileSystem = fileSystem; _fileSystem = fileSystem;
_path = path; _path = path;
_outputHeaders = outputHeaders; _outputHeaders = outputHeaders;
_logger = logger; _logger = logger;
_cancellationToken = cancellationToken;
_environment = environment; _environment = environment;
} }
public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, ILogger logger, IEnvironmentInfo environment)
{ {
_directStreamProvider = directStreamProvider; _directStreamProvider = directStreamProvider;
_outputHeaders = outputHeaders; _outputHeaders = outputHeaders;
_logger = logger; _logger = logger;
_cancellationToken = cancellationToken;
_environment = environment; _environment = environment;
} }
@ -69,8 +66,6 @@ namespace MediaBrowser.Api.LiveTv
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
{ {
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token;
if (_directStreamProvider != null) if (_directStreamProvider != null)
{ {
await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
@ -89,7 +84,9 @@ namespace MediaBrowser.Api.LiveTv
inputStream.Position = StartPosition; inputStream.Position = StartPosition;
} }
while (eofCount < 20 || !AllowEndOfFile) var emptyReadLimit = AllowEndOfFile ? 20 : 100;
while (eofCount < emptyReadLimit)
{ {
int bytesRead; int bytesRead;
if (allowAsyncFileRead) if (allowAsyncFileRead)

View file

@ -13,6 +13,7 @@ using System.Threading.Tasks;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.MediaInfo;
namespace MediaBrowser.Providers.Manager namespace MediaBrowser.Providers.Manager
{ {
@ -37,6 +38,28 @@ namespace MediaBrowser.Providers.Manager
LibraryManager = libraryManager; LibraryManager = libraryManager;
} }
private bool RequiresRefresh(IHasMetadata item, IDirectoryService directoryService)
{
if (item.RequiresRefresh())
{
return true;
}
if (item.SupportsLocalMetadata)
{
var video = item as Video;
if (video != null && !video.IsPlaceHolder)
{
return !video.SubtitleFiles
.SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, directoryService, FileSystem, false)
.OrderBy(i => i), StringComparer.OrdinalIgnoreCase);
}
}
return false;
}
public async Task<ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) public async Task<ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{ {
var itemOfType = (TItemType)item; var itemOfType = (TItemType)item;
@ -47,19 +70,35 @@ namespace MediaBrowser.Providers.Manager
var libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item); var libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item);
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) DateTime? newDateModified = null;
if (item.LocationType == LocationType.FileSystem)
{ {
// TODO: If this returns true, should we instead just change metadata refresh mode to Full? var file = refreshOptions.DirectoryService.GetFile(item.Path);
requiresRefresh = item.RequiresRefresh(); if (file != null)
{
newDateModified = file.LastWriteTimeUtc;
if (item.EnableRefreshOnDateModifiedChange)
{
if (newDateModified != item.DateModified)
{
Logger.Debug("Date modified for {0}. Old date {1} new date {2} Id {3}", item.Path, item.DateModified, newDateModified, item.Id);
requiresRefresh = true;
}
}
}
} }
if (!requiresRefresh && if (!requiresRefresh && libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays)
libraryOptions.AutomaticRefreshIntervalDays > 0 &&
(DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays)
{ {
requiresRefresh = true; requiresRefresh = true;
} }
if (!requiresRefresh && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
{
// TODO: If this returns true, should we instead just change metadata refresh mode to Full?
requiresRefresh = RequiresRefresh(item, refreshOptions.DirectoryService);
}
var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem); var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem);
var localImagesFailed = false; var localImagesFailed = false;
@ -145,20 +184,9 @@ namespace MediaBrowser.Providers.Manager
var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh, updateType); var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh, updateType);
updateType = updateType | beforeSaveResult; updateType = updateType | beforeSaveResult;
if (item.LocationType == LocationType.FileSystem) if (newDateModified.HasValue)
{ {
var file = refreshOptions.DirectoryService.GetFile(item.Path); item.DateModified = newDateModified.Value;
if (file != null)
{
var fileLastWriteTime = file.LastWriteTimeUtc;
if (item.EnableRefreshOnDateModifiedChange && fileLastWriteTime != item.DateModified)
{
Logger.Debug("Date modified for {0}. Old date {1} new date {2} Id {3}", item.Path, item.DateModified, fileLastWriteTime, item.Id);
requiresRefresh = true;
}
item.DateModified = fileLastWriteTime;
}
} }
// Save if changes were made, or it's never been saved before // Save if changes were made, or it's never been saved before

View file

@ -350,8 +350,6 @@ namespace MediaBrowser.Providers.TV
foreach (var episodeToRemove in episodesToRemove.Select(e => e.Episode)) foreach (var episodeToRemove in episodesToRemove.Select(e => e.Episode))
{ {
_logger.Info("Removing missing/unaired episode {0} {1}x{2}", episodeToRemove.Series.Name, episodeToRemove.ParentIndexNumber, episodeToRemove.IndexNumber);
await episodeToRemove.Delete(new DeleteOptions await episodeToRemove.Delete(new DeleteOptions
{ {
DeleteFileLocation = true DeleteFileLocation = true
@ -418,8 +416,6 @@ namespace MediaBrowser.Providers.TV
foreach (var seasonToRemove in seasonsToRemove) foreach (var seasonToRemove in seasonsToRemove)
{ {
_logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber);
await seasonToRemove.Delete(new DeleteOptions await seasonToRemove.Delete(new DeleteOptions
{ {
DeleteFileLocation = true DeleteFileLocation = true