aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs32
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs7
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs4
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj7
-rw-r--r--Emby.Server.Implementations/HttpServer/FileWriter.cs191
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs11
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs42
-rw-r--r--Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs6
-rw-r--r--Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs8
-rw-r--r--Emby.Server.Implementations/IO/FileRefresher.cs8
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs2
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs65
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs91
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs66
-rw-r--r--Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs27
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs158
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs115
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs125
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs10
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs17
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs19
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs1
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs32
-rw-r--r--Emby.Server.Implementations/Services/ResponseHelper.cs82
-rw-r--r--Emby.Server.Implementations/Windows/LoopUtil.cs358
-rw-r--r--Emby.Server.Implementations/packages.config2
30 files changed, 1092 insertions, 417 deletions
diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
index 2819a249f..0096f2284 100644
--- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
+++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities.Audio;
@@ -22,13 +23,15 @@ namespace Emby.Server.Implementations.Data
private readonly IItemRepository _itemRepo;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
+ private readonly IApplicationPaths _appPaths;
- public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem)
+ public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
{
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_logger = logger;
_fileSystem = fileSystem;
+ _appPaths = appPaths;
}
public string Name
@@ -150,13 +153,27 @@ namespace Emby.Server.Implementations.Data
try
{
- if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
+ var isPathInLibrary = false;
+
+ if (allLibraryPaths.Any(i => path.StartsWith(i, StringComparison.Ordinal)) ||
+ allLibraryPaths.Contains(path, StringComparer.Ordinal) ||
+ path.StartsWith(_appPaths.ProgramDataPath, StringComparison.Ordinal))
{
- continue;
+ isPathInLibrary = true;
+
+ if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
+ {
+ continue;
+ }
}
var libraryItem = _libraryManager.GetItemById(item.Item1);
+ if (libraryItem == null)
+ {
+ continue;
+ }
+
if (libraryItem.IsTopParent)
{
continue;
@@ -180,7 +197,14 @@ namespace Emby.Server.Implementations.Data
continue;
}
- _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+ if (isPathInLibrary)
+ {
+ _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+ }
+ else
+ {
+ _logger.Info("Deleting item from database {0} because path is no longer in the server library. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
+ }
await libraryItem.OnFileDeleted().ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index 88c0ea203..588b42a09 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -195,13 +195,12 @@ namespace Emby.Server.Implementations.Devices
}
var config = _config.GetUploadOptions();
- if (!string.IsNullOrWhiteSpace(config.CameraUploadPath))
+ var path = config.CameraUploadPath;
+ if (string.IsNullOrWhiteSpace(path))
{
- return config.CameraUploadPath;
+ path = DefaultCameraUploadsPath;
}
- var path = DefaultCameraUploadsPath;
-
if (config.EnableCameraUploadSubfolders)
{
path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name));
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 8b6b388db..d477008a5 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -491,7 +491,7 @@ namespace Emby.Server.Implementations.Dto
}
}
- if (!(item is LiveTvProgram))
+ if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess))
{
dto.PlayAccess = item.GetPlayAccess(user);
}
@@ -1639,7 +1639,7 @@ namespace Emby.Server.Implementations.Dto
var width = size.Width;
var height = size.Height;
- if (width == 0 || height == 0)
+ if (width.Equals(0) || height.Equals(0))
{
return null;
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index c704d0e4e..ecd86d507 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -84,6 +84,7 @@
<Compile Include="FileOrganization\NameUtils.cs" />
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
<Compile Include="FileOrganization\TvFolderOrganizer.cs" />
+ <Compile Include="HttpServer\FileWriter.cs" />
<Compile Include="HttpServer\GetSwaggerResource.cs" />
<Compile Include="HttpServer\HttpListenerHost.cs" />
<Compile Include="HttpServer\HttpResultFactory.cs" />
@@ -170,7 +171,6 @@
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" />
- <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" />
@@ -269,6 +269,7 @@
<Compile Include="Updates\InstallationManager.cs" />
<Compile Include="UserViews\CollectionFolderImageProvider.cs" />
<Compile Include="UserViews\DynamicImageProvider.cs" />
+ <Compile Include="Windows\LoopUtil.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Localization\iso6392.txt" />
@@ -302,8 +303,8 @@
<HintPath>..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
<Private>True</Private>
</Reference>
- <Reference Include="MediaBrowser.Naming, Version=1.0.6201.24431, Culture=neutral, processorArchitecture=MSIL">
- <HintPath>..\packages\MediaBrowser.Naming.1.0.4\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
+ <Reference Include="MediaBrowser.Naming, Version=1.0.6279.25941, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCL.pretty, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
new file mode 100644
index 000000000..d230a9b91
--- /dev/null
+++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Services;
+
+namespace Emby.Server.Implementations.HttpServer
+{
+ public class FileWriter : IHttpResult
+ {
+ private ILogger Logger { get; set; }
+
+ private string RangeHeader { get; set; }
+ private bool IsHeadRequest { get; set; }
+
+ private long RangeStart { get; set; }
+ private long RangeEnd { get; set; }
+ private long RangeLength { get; set; }
+ private long TotalContentLength { get; set; }
+
+ public Action OnComplete { get; set; }
+ public Action OnError { get; set; }
+ private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+ public List<Cookie> Cookies { get; private set; }
+
+ public FileShareMode FileShare { get; set; }
+
+ /// <summary>
+ /// The _options
+ /// </summary>
+ private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
+ /// <summary>
+ /// Gets the options.
+ /// </summary>
+ /// <value>The options.</value>
+ public IDictionary<string, string> Headers
+ {
+ get { return _options; }
+ }
+
+ public string Path { get; set; }
+
+ public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ {
+ throw new ArgumentNullException("contentType");
+ }
+
+ Path = path;
+ Logger = logger;
+ RangeHeader = rangeHeader;
+
+ Headers["Content-Type"] = contentType;
+
+ TotalContentLength = fileSystem.GetFileInfo(path).Length;
+
+ if (string.IsNullOrWhiteSpace(rangeHeader))
+ {
+ Headers["Content-Length"] = TotalContentLength.ToString(UsCulture);
+ StatusCode = HttpStatusCode.OK;
+ }
+ else
+ {
+ Headers["Accept-Ranges"] = "bytes";
+ StatusCode = HttpStatusCode.PartialContent;
+ SetRangeValues();
+ }
+
+ FileShare = FileShareMode.Read;
+ Cookies = new List<Cookie>();
+ }
+
+ /// <summary>
+ /// Sets the range values.
+ /// </summary>
+ private void SetRangeValues()
+ {
+ var requestedRange = RequestedRanges[0];
+
+ // If the requested range is "0-", we can optimize by just doing a stream copy
+ if (!requestedRange.Value.HasValue)
+ {
+ RangeEnd = TotalContentLength - 1;
+ }
+ else
+ {
+ RangeEnd = requestedRange.Value.Value;
+ }
+
+ RangeStart = requestedRange.Key;
+ RangeLength = 1 + RangeEnd - RangeStart;
+
+ // Content-Length is the length of what we're serving, not the original content
+ Headers["Content-Length"] = RangeLength.ToString(UsCulture);
+ Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
+ }
+
+ /// <summary>
+ /// The _requested ranges
+ /// </summary>
+ private List<KeyValuePair<long, long?>> _requestedRanges;
+ /// <summary>
+ /// Gets the requested ranges.
+ /// </summary>
+ /// <value>The requested ranges.</value>
+ protected List<KeyValuePair<long, long?>> RequestedRanges
+ {
+ get
+ {
+ if (_requestedRanges == null)
+ {
+ _requestedRanges = new List<KeyValuePair<long, long?>>();
+
+ // Example: bytes=0-,32-63
+ var ranges = RangeHeader.Split('=')[1].Split(',');
+
+ foreach (var range in ranges)
+ {
+ var vals = range.Split('-');
+
+ long start = 0;
+ long? end = null;
+
+ if (!string.IsNullOrEmpty(vals[0]))
+ {
+ start = long.Parse(vals[0], UsCulture);
+ }
+ if (!string.IsNullOrEmpty(vals[1]))
+ {
+ end = long.Parse(vals[1], UsCulture);
+ }
+
+ _requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
+ }
+ }
+
+ return _requestedRanges;
+ }
+ }
+
+ public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken)
+ {
+ try
+ {
+ // Headers only
+ if (IsHeadRequest)
+ {
+ return;
+ }
+
+ if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
+ {
+ Logger.Info("Transmit file {0}", Path);
+ await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
+ return;
+ }
+
+ await response.TransmitFile(Path, RangeStart, RangeEnd, FileShare, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ if (OnComplete != null)
+ {
+ OnComplete();
+ }
+ }
+ }
+
+ public string ContentType { get; set; }
+
+ public IRequest RequestContext { get; set; }
+
+ public object Response { get; set; }
+
+ public int Status { get; set; }
+
+ public HttpStatusCode StatusCode
+ {
+ get { return (HttpStatusCode)Status; }
+ set { Status = (int)value; }
+ }
+
+ public string StatusDescription { get; set; }
+
+ }
+}
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 6fcdab874..6d15cc619 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.HttpServer
private readonly ISocketFactory _socketFactory;
private readonly ICryptoProvider _cryptoProvider;
+ private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
private readonly IXmlSerializer _xmlSerializer;
private readonly ICertificate _certificate;
@@ -70,8 +71,7 @@ namespace Emby.Server.Implementations.HttpServer
ILogger logger,
IServerConfigurationManager config,
string serviceName,
- string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets)
- : base()
+ string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem)
{
Instance = this;
@@ -89,6 +89,7 @@ namespace Emby.Server.Implementations.HttpServer
_streamFactory = streamFactory;
_funcParseFn = funcParseFn;
_enableDualModeSockets = enableDualModeSockets;
+ _fileSystem = fileSystem;
_config = config;
_logger = logger;
@@ -226,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer
_cryptoProvider,
_streamFactory,
_enableDualModeSockets,
- GetRequest);
+ GetRequest,
+ _fileSystem);
}
private IHttpRequest GetRequest(HttpListenerContext httpContext)
@@ -579,7 +581,7 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
- ErrorHandler(new FileNotFoundException(), httpReq);
+ ErrorHandler(new FileNotFoundException(), httpReq, false);
}
}
catch (OperationCanceledException ex)
@@ -633,7 +635,6 @@ namespace Emby.Server.Implementations.HttpServer
return null;
}
-
private void Write(IResponse response, string text)
{
var bOutput = Encoding.UTF8.GetBytes(text);
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index 6bfd83110..310161d41 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -474,10 +474,6 @@ namespace Emby.Server.Implementations.HttpServer
{
throw new ArgumentNullException("cacheKey");
}
- if (options.ContentFactory == null)
- {
- throw new ArgumentNullException("factoryFn");
- }
var key = cacheKey.ToString("N");
@@ -560,30 +556,44 @@ namespace Emby.Server.Implementations.HttpServer
{
var rangeHeader = requestContext.Headers.Get("Range");
- var stream = await factoryFn().ConfigureAwait(false);
+ if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path))
+ {
+ return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
+ {
+ OnComplete = options.OnComplete,
+ OnError = options.OnError,
+ FileShare = options.FileShare
+ };
+ }
if (!string.IsNullOrEmpty(rangeHeader))
{
+ var stream = await factoryFn().ConfigureAwait(false);
+
return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger)
{
OnComplete = options.OnComplete
};
}
+ else
+ {
+ var stream = await factoryFn().ConfigureAwait(false);
- responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
+ responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
- if (isHeadRequest)
- {
- stream.Dispose();
+ if (isHeadRequest)
+ {
+ stream.Dispose();
- return GetHttpResult(new byte[] { }, contentType, true);
- }
+ return GetHttpResult(new byte[] { }, contentType, true);
+ }
- return new StreamWriter(stream, contentType, _logger)
- {
- OnComplete = options.OnComplete,
- OnError = options.OnError
- };
+ return new StreamWriter(stream, contentType, _logger)
+ {
+ OnComplete = options.OnComplete,
+ OnError = options.OnError
+ };
+ }
}
using (var stream = await factoryFn().ConfigureAwait(false))
diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
index 652fc4f83..b11b2fe88 100644
--- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
+++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs
@@ -27,10 +27,11 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
private readonly ISocketFactory _socketFactory;
private readonly ICryptoProvider _cryptoProvider;
private readonly IStreamFactory _streamFactory;
+ private readonly IFileSystem _fileSystem;
private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory;
private readonly bool _enableDualMode;
- public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory)
+ public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem)
{
_logger = logger;
_certificate = certificate;
@@ -42,6 +43,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
_streamFactory = streamFactory;
_enableDualMode = enableDualMode;
_httpRequestFactory = httpRequestFactory;
+ _fileSystem = fileSystem;
}
public Action<Exception, IRequest, bool> ErrorHandler { get; set; }
@@ -54,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void Start(IEnumerable<string> urlPrefixes)
{
if (_listener == null)
- _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider);
+ _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem);
_listener.EnableDualMode = _enableDualMode;
diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
index 36f795411..fd30b227f 100644
--- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
+++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
@@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using SocketHttpListener.Net;
using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
@@ -189,5 +192,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void ClearCookies()
{
}
+
+ public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
+ {
+ return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken);
+ }
}
}
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index d7f2ffa43..c64672685 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -160,10 +160,10 @@ namespace Emby.Server.Implementations.IO
.DistinctBy(i => i.Id)
.ToList();
- foreach (var p in paths)
- {
- Logger.Info(p + " reports change.");
- }
+ //foreach (var p in paths)
+ //{
+ // Logger.Info(p + " reports change.");
+ //}
// If the root folder changed, run the library task so the user can see it
if (itemsToRefresh.Any(i => i is AggregateFolder))
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index 7a36691df..38908c2bd 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Images
{
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
}
- if (item is Playlist || item is MusicGenre)
+ if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre)
{
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 56bffc233..026486efc 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -409,24 +409,46 @@ namespace Emby.Server.Implementations.Library
if (options.DeleteFileLocation && locationType != LocationType.Remote && locationType != LocationType.Virtual)
{
+ // Assume only the first is required
+ // Add this flag to GetDeletePaths if required in the future
+ var isRequiredForDelete = true;
+
foreach (var fileSystemInfo in item.GetDeletePaths().ToList())
{
- if (fileSystemInfo.IsDirectory)
+ try
{
- _logger.Debug("Deleting path {0}", fileSystemInfo.FullName);
- _fileSystem.DeleteDirectory(fileSystemInfo.FullName, true);
+ if (fileSystemInfo.IsDirectory)
+ {
+ _logger.Debug("Deleting path {0}", fileSystemInfo.FullName);
+ _fileSystem.DeleteDirectory(fileSystemInfo.FullName, true);
+ }
+ else
+ {
+ _logger.Debug("Deleting path {0}", fileSystemInfo.FullName);
+ _fileSystem.DeleteFile(fileSystemInfo.FullName);
+ }
}
- else
+ catch (IOException)
+ {
+ if (isRequiredForDelete)
+ {
+ throw;
+ }
+ }
+ catch (UnauthorizedAccessException)
{
- _logger.Debug("Deleting path {0}", fileSystemInfo.FullName);
- _fileSystem.DeleteFile(fileSystemInfo.FullName);
+ if (isRequiredForDelete)
+ {
+ throw;
+ }
}
+
+ isRequiredForDelete = false;
}
if (parent != null)
{
- await parent.ValidateChildren(new Progress<double>(), CancellationToken.None)
- .ConfigureAwait(false);
+ await parent.ValidateChildren(new Progress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false) .ConfigureAwait(false);
}
}
else if (parent != null)
@@ -492,6 +514,11 @@ namespace Emby.Server.Implementations.Library
public Guid GetNewItemId(string key, Type type)
{
+ return GetNewItemIdInternal(key, type, false);
+ }
+
+ private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive)
+ {
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentNullException("key");
@@ -509,7 +536,7 @@ namespace Emby.Server.Implementations.Library
.Replace("/", "\\");
}
- if (!ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
+ if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
{
key = key.ToLower();
}
@@ -843,7 +870,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Person}.</returns>
public Person GetPerson(string name)
{
- return CreateItemByName<Person>(Person.GetPath(name), name);
+ return CreateItemByName<Person>(Person.GetPath, name);
}
/// <summary>
@@ -853,7 +880,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Studio}.</returns>
public Studio GetStudio(string name)
{
- return CreateItemByName<Studio>(Studio.GetPath(name), name);
+ return CreateItemByName<Studio>(Studio.GetPath, name);
}
/// <summary>
@@ -863,7 +890,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Genre}.</returns>
public Genre GetGenre(string name)
{
- return CreateItemByName<Genre>(Genre.GetPath(name), name);
+ return CreateItemByName<Genre>(Genre.GetPath, name);
}
/// <summary>
@@ -873,7 +900,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{MusicGenre}.</returns>
public MusicGenre GetMusicGenre(string name)
{
- return CreateItemByName<MusicGenre>(MusicGenre.GetPath(name), name);
+ return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name);
}
/// <summary>
@@ -883,7 +910,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{GameGenre}.</returns>
public GameGenre GetGameGenre(string name)
{
- return CreateItemByName<GameGenre>(GameGenre.GetPath(name), name);
+ return CreateItemByName<GameGenre>(GameGenre.GetPath, name);
}
/// <summary>
@@ -901,7 +928,7 @@ namespace Emby.Server.Implementations.Library
var name = value.ToString(CultureInfo.InvariantCulture);
- return CreateItemByName<Year>(Year.GetPath(name), name);
+ return CreateItemByName<Year>(Year.GetPath, name);
}
/// <summary>
@@ -911,10 +938,10 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Genre}.</returns>
public MusicArtist GetArtist(string name)
{
- return CreateItemByName<MusicArtist>(MusicArtist.GetPath(name), name);
+ return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name);
}
- private T CreateItemByName<T>(string path, string name)
+ private T CreateItemByName<T>(Func<string,string> getPathFn, string name)
where T : BaseItem, new()
{
if (typeof(T) == typeof(MusicArtist))
@@ -935,7 +962,9 @@ namespace Emby.Server.Implementations.Library
}
}
- var id = GetNewItemId(path, typeof(T));
+ var path = getPathFn(name);
+ var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
+ var id = GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
var item = GetItemById(id) as T;
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index eb0d0cf9b..3b11a4767 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -434,6 +434,11 @@ namespace Emby.Server.Implementations.Library
Policy = user.Policy
};
+ if (!hasPassword && Users.Count() == 1)
+ {
+ dto.EnableAutoLogin = true;
+ }
+
var image = user.GetImageInfo(ImageType.Primary, 0);
if (image != null)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 926d82f94..eea562524 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1276,6 +1276,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
+ var registration = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false);
+ if (!registration.IsValid)
+ {
+ _logger.Warn("Emby Premiere required to use Emby DVR.");
+ OnTimerOutOfDate(timer);
+ return;
+ }
+
var activeRecordingInfo = new ActiveRecordingInfo
{
CancellationTokenSource = new CancellationTokenSource(),
@@ -2319,6 +2327,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
UpdateExistingTimerWithNewMetadata(existingTimer, timer);
+ // Needed by ShouldCancelTimerForSeriesTimer
+ timer.IsManual = existingTimer.IsManual;
+
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
{
existingTimer.Status = RecordingStatus.Cancelled;
@@ -2531,6 +2542,86 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public ProgramInfo Program { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; }
}
+
+ private const int TunerDiscoveryDurationMs = 3000;
+
+ public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
+ {
+ var list = new List<TunerHostInfo>();
+
+ var configuredDeviceIds = GetConfiguration().TunerHosts
+ .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
+ .Select(i => i.DeviceId)
+ .ToList();
+
+ foreach (var host in _liveTvManager.TunerHosts)
+ {
+ var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
+
+ if (newDevicesOnly)
+ {
+ discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase))
+ .ToList();
+ }
+ list.AddRange(discoveredDevices);
+ }
+
+ return list;
+ }
+
+ public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
+ {
+ foreach (var host in _liveTvManager.TunerHosts)
+ {
+ await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
+ {
+ var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
+
+ var configuredDevices = GetConfiguration().TunerHosts
+ .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ foreach (var device in discoveredDevices)
+ {
+ var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
+
+ if (configuredDevice != null)
+ {
+ if (!string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.Info("Tuner url has changed from {0} to {1}", configuredDevice.Url, device.Url);
+
+ configuredDevice.Url = device.Url;
+ await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+
+ private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDuationMs, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var discoveredDevices = await host.DiscoverDevices(discoveryDuationMs, cancellationToken).ConfigureAwait(false);
+
+ foreach (var device in discoveredDevices)
+ {
+ _logger.Info("Discovered tuner device {0} at {1}", host.Name, device.Url);
+ }
+
+ return discoveredDevices;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error discovering tuner devices", ex);
+
+ return new List<TunerHostInfo>();
+ }
+ }
}
public static class ConfigurationExtension
{
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index c22bb1171..21c4006a6 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -205,6 +205,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
programInfo.ShowId = uniqueString.GetMD5().ToString("N");
+
+ // If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped
+ if (programInfo.IsSeries && !programInfo.IsRepeat)
+ {
+ if ((programInfo.EpisodeNumber ?? 0) == 0)
+ {
+ programInfo.ShowId = programInfo.ShowId + programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture);
+ }
+ }
}
// Construct an id from the channel and start date
diff --git a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs b/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs
index e2f973699..9a8a930bd 100644
--- a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs
@@ -6,7 +6,6 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
namespace Emby.Server.Implementations.LiveTv
@@ -16,6 +15,8 @@ namespace Emby.Server.Implementations.LiveTv
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger;
+ const int AnalyzeDurationMs = 2000;
+
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
{
_mediaEncoder = mediaEncoder;
@@ -34,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv
Protocol = mediaSource.Protocol,
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
ExtractChapters = false,
- AnalyzeDurationSections = 2
+ AnalyzeDurationMs = AnalyzeDurationMs
}, cancellationToken).ConfigureAwait(false);
@@ -98,6 +99,8 @@ namespace Emby.Server.Implementations.LiveTv
// Try to estimate this
mediaSource.InferTotalBitrate(true);
+
+ mediaSource.AnalyzeDurationMs = AnalyzeDurationMs;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 654777b3c..8406d44a7 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -150,6 +150,21 @@ namespace Emby.Server.Implementations.LiveTv
get { return _listingProviders; }
}
+ public List<NameIdPair> GetTunerHostTypes()
+ {
+ return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
+ {
+ Name = i.Name,
+ Id = i.Type
+
+ }).ToList();
+ }
+
+ public Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
+ {
+ return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken);
+ }
+
void service_DataSourceChanged(object sender, EventArgs e)
{
if (!_isDisposed)
@@ -1180,6 +1195,8 @@ namespace Emby.Server.Implementations.LiveTv
{
EmbyTV.EmbyTV.Current.CreateRecordingFolders();
+ await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
+
var numComplete = 0;
double progressPerService = _services.Count == 0
? 0
@@ -1596,6 +1613,7 @@ namespace Emby.Server.Implementations.LiveTv
IsFolder = false,
IsVirtualItem = false,
Limit = query.Limit,
+ StartIndex = query.StartIndex,
SortBy = new[] { ItemSortBy.DateCreated },
SortOrder = SortOrder.Descending,
EnableTotalRecordCount = query.EnableTotalRecordCount,
@@ -2747,7 +2765,7 @@ namespace Emby.Server.Implementations.LiveTv
private bool IsLiveTvEnabled(User user)
{
- return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count(i => i.IsEnabled) > 0);
+ return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count > 0);
}
public IEnumerable<User> GetEnabledUsers()
@@ -2985,7 +3003,7 @@ namespace Emby.Server.Implementations.LiveTv
if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
{
var config = GetConfiguration();
- if (config.TunerHosts.Count(i => i.IsEnabled) > 0 &&
+ if (config.TunerHosts.Count > 0 &&
config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
{
return Task.FromResult(new MBRegistrationRecord
@@ -2999,50 +3017,6 @@ namespace Emby.Server.Implementations.LiveTv
return _security.GetRegistrationStatus(feature);
}
- public List<NameValuePair> GetSatIniMappings()
- {
- return new List<NameValuePair>();
- //var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini", StringComparison.OrdinalIgnoreCase) != -1).ToList();
-
- //return names.Select(GetSatIniMappings).Where(i => i != null).DistinctBy(i => i.Value.Split('|')[0]).ToList();
- }
-
- public NameValuePair GetSatIniMappings(string resource)
- {
- return new NameValuePair();
- //using (var stream = GetType().Assembly.GetManifestResourceStream(resource))
- //{
- // using (var reader = new StreamReader(stream))
- // {
- // var parser = new StreamIniDataParser();
- // IniData data = parser.ReadData(reader);
-
- // var satType1 = data["SATTYPE"]["1"];
- // var satType2 = data["SATTYPE"]["2"];
-
- // if (string.IsNullOrWhiteSpace(satType2))
- // {
- // return null;
- // }
-
- // var srch = "SatIp.ini.";
- // var filename = Path.GetFileName(resource);
-
- // return new NameValuePair
- // {
- // Name = satType1 + " " + satType2,
- // Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length)
- // };
- // }
- //}
- }
-
- public Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
- {
- return Task.FromResult(new List<ChannelInfo>());
- //return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
- }
-
public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
{
var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
index f2806292d..5582d8f35 100644
--- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
+++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv
public bool IsHidden
{
- get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count(i => i.IsEnabled) == 0; }
+ get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count == 0; }
}
public bool IsEnabled
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 5ac3812b0..c9cd289e7 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected virtual List<TunerHostInfo> GetTunerHosts()
{
return GetConfiguration().TunerHosts
- .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
+ .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
.ToList();
}
@@ -135,8 +135,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
// Check to make sure the tuner is available
// If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error
- if (hostsWithChannel.Count > 1 &&
- !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
+ if (hostsWithChannel.Count > 1 && !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
{
Logger.Error("Tuner is not currently available");
continue;
@@ -208,6 +207,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
foreach (var host in hostsWithChannel)
{
+ if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
try
{
var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
@@ -243,7 +247,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
- protected abstract bool IsValidChannelId(string channelId);
+ protected virtual string ChannelIdPrefix
+ {
+ get
+ {
+ return Type + "_";
+ }
+ }
+ protected virtual bool IsValidChannelId(string channelId)
+ {
+ if (string.IsNullOrWhiteSpace(channelId))
+ {
+ throw new ArgumentNullException("channelId");
+ }
+
+ return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
+ }
protected LiveTvOptions GetConfiguration()
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
deleted file mode 100644
index 336469c50..000000000
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
+++ /dev/null
@@ -1,158 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Linq;
-using System.Threading;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Serialization;
-
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
-{
- public class HdHomerunDiscovery : IServerEntryPoint
- {
- private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly IServerConfigurationManager _config;
- private readonly ILogger _logger;
- private readonly ILiveTvManager _liveTvManager;
- private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
- private readonly IHttpClient _httpClient;
- private readonly IJsonSerializer _json;
-
- public HdHomerunDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
- {
- _deviceDiscovery = deviceDiscovery;
- _config = config;
- _logger = logger;
- _liveTvManager = liveTvManager;
- _httpClient = httpClient;
- _json = json;
- }
-
- public void Run()
- {
- _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
- }
-
- void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
- {
- string server = null;
- var info = e.Argument;
-
- if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
- {
- string location;
- if (info.Headers.TryGetValue("Location", out location))
- {
- //_logger.Debug("HdHomerun found at {0}", location);
-
- // Just get the beginning of the url
- Uri uri;
- if (Uri.TryCreate(location, UriKind.Absolute, out uri))
- {
- var apiUrl = location.Replace(uri.LocalPath, String.Empty, StringComparison.OrdinalIgnoreCase)
- .TrimEnd('/');
-
- //_logger.Debug("HdHomerun api url: {0}", apiUrl);
- AddDevice(apiUrl);
- }
- }
- }
- }
-
- private async void AddDevice(string url)
- {
- await _semaphore.WaitAsync().ConfigureAwait(false);
-
- try
- {
- var options = GetConfiguration();
-
- if (options.TunerHosts.Any(i =>
- string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) &&
- UriEquals(i.Url, url)))
- {
- return;
- }
-
- // Strip off the port
- url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/');
-
- // Test it by pulling down the lineup
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = string.Format("{0}/discover.json", url),
- CancellationToken = CancellationToken.None,
- BufferContent = false
- }))
- {
- var response = _json.DeserializeFromStream<HdHomerunHost.DiscoverResponse>(stream);
-
- var existing = GetConfiguration().TunerHosts
- .FirstOrDefault(i => string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.DeviceId, response.DeviceID, StringComparison.OrdinalIgnoreCase));
-
- if (existing == null)
- {
- await _liveTvManager.SaveTunerHost(new TunerHostInfo
- {
- Type = HdHomerunHost.DeviceType,
- Url = url,
- DeviceId = response.DeviceID
-
- }).ConfigureAwait(false);
- }
- else
- {
- if (!string.Equals(existing.Url, url, StringComparison.OrdinalIgnoreCase))
- {
- existing.Url = url;
- await _liveTvManager.SaveTunerHost(existing).ConfigureAwait(false);
- }
- }
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving device", ex);
- }
- finally
- {
- _semaphore.Release();
- }
- }
-
- private bool UriEquals(string savedUri, string location)
- {
- return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase);
- }
-
- private string NormalizeUrl(string url)
- {
- if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- url = "http://" + url;
- }
-
- url = url.TrimEnd('/');
-
- // Strip off the port
- return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
- }
-
- private LiveTvOptions GetConfiguration()
- {
- return _config.GetConfiguration<LiveTvOptions>("livetv");
- }
-
- public void Dispose()
- {
- }
- }
-}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index af3f1359f..d9c0807de 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -56,7 +56,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
get { return "hdhomerun"; }
}
- private const string ChannelIdPrefix = "hdhr_";
+ protected override string ChannelIdPrefix
+ {
+ get
+ {
+ return "hdhr_";
+ }
+ }
private string GetChannelId(TunerHostInfo info, Channels i)
{
@@ -69,9 +75,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
+ var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+
var options = new HttpRequestOptions
{
- Url = string.Format("{0}/lineup.json", GetApiUrl(info, false)),
+ Url = model.LineupURL,
CancellationToken = cancellationToken,
BufferContent = false
};
@@ -213,7 +221,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var list = new List<LiveTvTunerInfo>();
foreach (var host in GetConfiguration().TunerHosts
- .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
+ .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
{
try
{
@@ -451,7 +459,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
string nal = null;
- var url = info.Url;
+ var url = GetApiUrl(info, false);
var id = channelId;
id += "_" + url.GetMD5().ToString("N");
@@ -557,26 +565,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return list;
}
- protected override bool IsValidChannelId(string channelId)
- {
- if (string.IsNullOrWhiteSpace(channelId))
- {
- throw new ArgumentNullException("channelId");
- }
-
- return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
- }
-
protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
{
var profile = streamId.Split('_')[0];
Logger.Info("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelId, streamId, profile);
- if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
- {
- throw new ArgumentException("Channel not found");
- }
var hdhrId = GetHdHrIdFromChannelId(channelId);
var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
@@ -586,28 +580,23 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
- var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
+ var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- var liveStream = new HdHomerunUdpStream(mediaSource, streamId, hdhomerunChannel.Url, modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
- return liveStream;
+ return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
}
else
{
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
+ //var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- var liveStream = new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
- return liveStream;
+ return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
+ //return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
}
}
public async Task Validate(TunerHostInfo info)
{
- if (!info.IsEnabled)
- {
- return;
- }
-
lock (_modelCache)
{
_modelCache.Clear();
@@ -650,5 +639,75 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public string LineupURL { get; set; }
public int TunerCount { get; set; }
}
+
+ public async Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
+ {
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
+ var list = new List<TunerHostInfo>();
+
+ // Create udp broadcast discovery message
+ byte[] discBytes = { 0, 2, 0, 12, 1, 4, 255, 255, 255, 255, 2, 4, 255, 255, 255, 255, 115, 204, 125, 143 };
+ using (var udpClient = _socketFactory.CreateUdpBroadcastSocket(0))
+ {
+ // Need a way to set the Receive timeout on the socket otherwise this might never timeout?
+ try
+ {
+ await udpClient.SendAsync(discBytes, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ var deviceIp = response.RemoteEndPoint.IpAddress.Address;
+
+ // check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte
+ if (response.ReceivedBytes > 13 && response.Buffer[1] == 3)
+ {
+ var deviceAddress = "http://" + deviceIp;
+
+ var info = await TryGetTunerHostInfo(deviceAddress, cancellationToken).ConfigureAwait(false);
+
+ if (info != null)
+ {
+ list.Add(info);
+ }
+ }
+ }
+
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch
+ {
+ // Socket timeout indicates all messages have been received.
+ }
+ }
+
+ return list;
+ }
+
+ private async Task<TunerHostInfo> TryGetTunerHostInfo(string url, CancellationToken cancellationToken)
+ {
+ var hostInfo = new TunerHostInfo
+ {
+ Type = Type,
+ Url = url
+ };
+
+ try
+ {
+ var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
+
+ hostInfo.DeviceId = modelInfo.DeviceID;
+ hostInfo.FriendlyName = modelInfo.FriendlyName;
+
+ return hostInfo;
+ }
+ catch
+ {
+ // logged at lower levels
+ }
+
+ return null;
+ }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index a6e9491a4..2c678d9f8 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -9,6 +9,60 @@ using MediaBrowser.Model.Net;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
+ public interface IHdHomerunChannelCommands
+ {
+ IEnumerable<Tuple<string, string>> GetCommands();
+ }
+
+ public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
+ {
+ private string _channel;
+ private string _program;
+ public LegacyHdHomerunChannelCommands(string url)
+ {
+ // parse url for channel and program
+ var regExp = new Regex(@"\/ch(\d+)-?(\d*)");
+ var match = regExp.Match(url);
+ if (match.Success)
+ {
+ _channel = match.Groups[1].Value;
+ _program = match.Groups[2].Value;
+ }
+ }
+
+ public IEnumerable<Tuple<string, string>> GetCommands()
+ {
+ var commands = new List<Tuple<string, string>>();
+
+ if (!String.IsNullOrEmpty(_channel))
+ commands.Add(Tuple.Create("channel", _channel));
+
+ if (!String.IsNullOrEmpty(_program))
+ commands.Add(Tuple.Create("program", _program));
+ return commands;
+ }
+ }
+
+ public class HdHomerunChannelCommands : IHdHomerunChannelCommands
+ {
+ private string _channel;
+
+ public HdHomerunChannelCommands(string channel)
+ {
+ _channel = channel;
+ }
+
+ public IEnumerable<Tuple<string, string>> GetCommands()
+ {
+ var commands = new List<Tuple<string, string>>();
+
+ if (!String.IsNullOrEmpty(_channel))
+ commands.Add(Tuple.Create("vchannel", _channel));
+
+ return commands;
+ }
+ }
+
public class HdHomerunManager : IDisposable
{
public static int HdHomeRunPort = 65001;
@@ -57,16 +111,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase);
}
- public async Task StartStreaming(IpAddressInfo remoteIp, IpAddressInfo localIp, int localPort, string url, int numTuners, CancellationToken cancellationToken)
+ public async Task StartStreaming(IpAddressInfo remoteIp, IpAddressInfo localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken)
{
_remoteIp = remoteIp;
- // parse url for channel and program
- string frequency, program;
- if (!ParseUrl(url, out frequency, out program))
- {
- return;
- }
-
+
using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
{
if (!_lockkey.HasValue)
@@ -92,29 +140,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
continue;
- var channelMsg = CreateSetMessage(i, "channel", frequency, _lockkey.Value);
- await tcpClient.SendAsync(channelMsg, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
- await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
- // parse response to make sure it worked
- if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
+ var commandList = commands.GetCommands();
+ foreach(Tuple<string,string> command in commandList)
{
- await ReleaseLockkey(tcpClient).ConfigureAwait(false);
- continue;
- }
-
- if (program != String.Empty)
- {
- var programMsg = CreateSetMessage(i, "program", program, _lockkey.Value);
- await tcpClient.SendAsync(programMsg, programMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
- await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, _lockkey.Value);
+ await tcpClient.SendAsync(channelMsg, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
+ response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
{
await ReleaseLockkey(tcpClient).ConfigureAwait(false);
continue;
}
- }
+ }
+
var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort);
var targetMsg = CreateSetMessage(i, "target", targetValue, _lockkey.Value);
@@ -132,6 +172,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
+ public async Task ChangeChannel(IHdHomerunChannelCommands commands, CancellationToken cancellationToken)
+ {
+ if (!_lockkey.HasValue)
+ return;
+
+ using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
+ {
+ var commandList = commands.GetCommands();
+ foreach (Tuple<string, string> command in commandList)
+ {
+ var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey.Value);
+ await tcpClient.SendAsync(channelMsg, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false);
+ var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ // parse response to make sure it worked
+ string returnVal;
+ if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
+ {
+ return;
+ }
+ }
+ }
+ }
+
public async Task StopStreaming()
{
if (!_lockkey.HasValue)
@@ -154,22 +217,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
}
- private static bool ParseUrl(string url, out string frequency, out string program)
- {
- frequency = String.Empty;
- program = String.Empty;
- var regExp = new Regex(@"\/ch(\d+)-?(\d*)");
- var match = regExp.Match(url);
- if (match.Success)
- {
- frequency = match.Groups[1].Value;
- program = match.Groups[2].Value;
- return true;
- }
-
- return false;
- }
-
private static byte[] CreateGetMessage(int tuner, string name)
{
var byteName = Encoding.UTF8.GetBytes(String.Format("/tuner{0}/{1}\0", tuner, name));
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 8c749b1b5..a881d0ea1 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -29,11 +29,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
private readonly MulticastStream _multicastStream;
- private readonly string _channelUrl;
+ private readonly IHdHomerunChannelCommands _channelCommands;
private readonly int _numTuners;
private readonly INetworkManager _networkManager;
- public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, string channelUrl, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager)
+ public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager)
: base(mediaSource)
{
_fileSystem = fileSystem;
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_networkManager = networkManager;
OriginalStreamId = originalStreamId;
_multicastStream = new MulticastStream(_logger);
- _channelUrl = channelUrl;
+ _channelCommands = channelCommands;
_numTuners = numTuners;
}
@@ -118,10 +118,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
// send url to start streaming
- await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelUrl, _numTuners, cancellationToken).ConfigureAwait(false);
+ await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false);
var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
- _logger.Info("Opened HDHR UDP stream from {0}", _channelUrl);
+ _logger.Info("Opened HDHR UDP stream from {0}", remoteAddress);
if (!cancellationToken.IsCancellationRequested)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index a939cec7b..4ec70f802 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -46,8 +46,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
get { return "M3U Tuner"; }
}
- private const string ChannelIdPrefix = "m3u_";
-
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
{
var result = await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false);
@@ -87,16 +85,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- protected override bool IsValidChannelId(string channelId)
- {
- if (string.IsNullOrWhiteSpace(channelId))
- {
- throw new ArgumentNullException("channelId");
- }
-
- return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
- }
-
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{
var urlHash = info.Url.GetMD5().ToString("N");
@@ -176,5 +164,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
return Task.FromResult(true);
}
+
+ public Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(new List<TunerHostInfo>());
+ }
}
} \ No newline at end of file
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
index df83d4341..90ff36441 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
@@ -16,7 +16,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private const int BufferSize = 81920;
private CancellationToken _cancellationToken;
private readonly ILogger _logger;
- private readonly ConcurrentQueue<byte[]> _sharedBuffer = new ConcurrentQueue<byte[]>();
public MulticastStream(ILogger logger)
{
@@ -38,14 +37,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
- _sharedBuffer.Enqueue(copy);
-
- while (_sharedBuffer.Count > 3000)
- {
- byte[] bytes;
- _sharedBuffer.TryDequeue(out bytes);
- }
-
var allStreams = _outputStreams.ToList();
foreach (var stream in allStreams)
{
@@ -74,16 +65,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
OnFinished = OnFinished
};
- var list = new List<byte>();
- foreach (var bytes in _sharedBuffer)
- {
- list.AddRange(bytes);
- }
-
- _logger.Info("QueueStream started with {0} initial bytes", list.Count);
-
- result.Queue(list.ToArray());
-
_outputStreams.TryAdd(result.Id, result);
result.Start(_cancellationToken);
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 120f445c2..64a41acef 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -408,6 +408,7 @@ namespace Emby.Server.Implementations.Localization
new LocalizatonOption{ Name="Italian", Value="it"},
new LocalizatonOption{ Name="Kazakh", Value="kk"},
new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"},
+ new LocalizatonOption{ Name="Persian", Value="fa"},
new LocalizatonOption{ Name="Polish", Value="pl"},
new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
index ef7d6dba8..9514c12ca 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
@@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Library;
@@ -101,4 +102,35 @@ namespace Emby.Server.Implementations.Playlists
//}
}
+ public class GenreImageProvider : BaseDynamicImageProvider<Genre>
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
+ {
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Genres = new[] { item.Name },
+ IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name },
+ SortBy = new[] { ItemSortBy.Random },
+ Limit = 4,
+ Recursive = true,
+ ImageTypes = new[] { ImageType.Primary }
+
+ }).ToList();
+
+ return Task.FromResult(GetFinalItems(items));
+ }
+
+ //protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ //{
+ // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
+ //}
+ }
+
}
diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs
index d4ce1cabf..3c2af60db 100644
--- a/Emby.Server.Implementations/Services/ResponseHelper.cs
+++ b/Emby.Server.Implementations/Services/ResponseHelper.cs
@@ -12,45 +12,6 @@ namespace Emby.Server.Implementations.Services
{
public static class ResponseHelper
{
- private static async Task<bool> WriteToOutputStream(IResponse response, object result)
- {
- var asyncStreamWriter = result as IAsyncStreamWriter;
- if (asyncStreamWriter != null)
- {
- await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
- return true;
- }
-
- var streamWriter = result as IStreamWriter;
- if (streamWriter != null)
- {
- streamWriter.WriteTo(response.OutputStream);
- return true;
- }
-
- var stream = result as Stream;
- if (stream != null)
- {
- using (stream)
- {
- await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false);
- return true;
- }
- }
-
- var bytes = result as byte[];
- if (bytes != null)
- {
- response.ContentType = "application/octet-stream";
- response.SetContentLength(bytes.Length);
-
- await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
- return true;
- }
-
- return false;
- }
-
public static Task WriteToResponse(IResponse httpRes, IRequest httpReq, object result)
{
if (result == null)
@@ -141,16 +102,51 @@ namespace Emby.Server.Implementations.Services
response.ContentType += "; charset=utf-8";
}
- var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false);
- if (writeToOutputStreamResult)
+ var asyncStreamWriter = result as IAsyncStreamWriter;
+ if (asyncStreamWriter != null)
{
+ await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
+ return;
+ }
+
+ var streamWriter = result as IStreamWriter;
+ if (streamWriter != null)
+ {
+ streamWriter.WriteTo(response.OutputStream);
+ return;
+ }
+
+ var fileWriter = result as FileWriter;
+ if (fileWriter != null)
+ {
+ await fileWriter.WriteToAsync(response, CancellationToken.None).ConfigureAwait(false);
+ return;
+ }
+
+ var stream = result as Stream;
+ if (stream != null)
+ {
+ using (stream)
+ {
+ await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false);
+ return;
+ }
+ }
+
+ var bytes = result as byte[];
+ if (bytes != null)
+ {
+ response.ContentType = "application/octet-stream";
+ response.SetContentLength(bytes.Length);
+
+ await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
return;
}
var responseText = result as string;
if (responseText != null)
{
- var bytes = Encoding.UTF8.GetBytes(responseText);
+ bytes = Encoding.UTF8.GetBytes(responseText);
response.SetContentLength(bytes.Length);
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
return;
@@ -163,7 +159,7 @@ namespace Emby.Server.Implementations.Services
{
var contentType = request.ResponseContentType;
var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType);
-
+
using (var ms = new MemoryStream())
{
serializer(result, ms);
diff --git a/Emby.Server.Implementations/Windows/LoopUtil.cs b/Emby.Server.Implementations/Windows/LoopUtil.cs
new file mode 100644
index 000000000..6eded2cec
--- /dev/null
+++ b/Emby.Server.Implementations/Windows/LoopUtil.cs
@@ -0,0 +1,358 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Emby.Server.Implementations.Windows
+{
+ /// <summary>
+ /// http://blogs.msdn.com/b/fiddler/archive/2011/12/10/fiddler-windows-8-apps-enable-LoopUtil-network-isolation-exemption.aspx
+ /// </summary>
+ public class LoopUtil
+ {
+ //http://msdn.microsoft.com/en-us/library/windows/desktop/aa379595(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SID_AND_ATTRIBUTES
+ {
+ public IntPtr Sid;
+ public uint Attributes;
+ }
+
+ [StructLayoutAttribute(LayoutKind.Sequential)]
+ internal struct INET_FIREWALL_AC_CAPABILITIES
+ {
+ public uint count;
+ public IntPtr capabilities; //SID_AND_ATTRIBUTES
+ }
+
+ [StructLayoutAttribute(LayoutKind.Sequential)]
+ internal struct INET_FIREWALL_AC_BINARIES
+ {
+ public uint count;
+ public IntPtr binaries;
+ }
+
+ [StructLayoutAttribute(LayoutKind.Sequential)]
+ internal struct INET_FIREWALL_APP_CONTAINER
+ {
+ internal IntPtr appContainerSid;
+ internal IntPtr userSid;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string appContainerName;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string displayName;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string description;
+ internal INET_FIREWALL_AC_CAPABILITIES capabilities;
+ internal INET_FIREWALL_AC_BINARIES binaries;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string workingDirectory;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string packageFullName;
+ }
+
+
+ // Call this API to free the memory returned by the Enumeration API
+ [DllImport("FirewallAPI.dll")]
+ internal static extern void NetworkIsolationFreeAppContainers(IntPtr pACs);
+
+ // Call this API to load the current list of LoopUtil-enabled AppContainers
+ [DllImport("FirewallAPI.dll")]
+ internal static extern uint NetworkIsolationGetAppContainerConfig(out uint pdwCntACs, out IntPtr appContainerSids);
+
+ // Call this API to set the LoopUtil-exemption list
+ [DllImport("FirewallAPI.dll")]
+ private static extern uint NetworkIsolationSetAppContainerConfig(uint pdwCntACs, SID_AND_ATTRIBUTES[] appContainerSids);
+
+
+ // Use this API to convert a string SID into an actual SID
+ [DllImport("advapi32.dll", SetLastError = true)]
+ internal static extern bool ConvertStringSidToSid(string strSid, out IntPtr pSid);
+
+ [DllImport("advapi32", /*CharSet = CharSet.Auto,*/ SetLastError = true)]
+ static extern bool ConvertSidToStringSid(
+ [MarshalAs(UnmanagedType.LPArray)] byte[] pSID,
+ out IntPtr ptrSid);
+
+ [DllImport("advapi32", /*CharSet = CharSet.Auto,*/ SetLastError = true)]
+ static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid);
+
+ // Use this API to convert a string reference (e.g. "@{blah.pri?ms-resource://whatever}") into a plain string
+ [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+ internal static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf);
+
+ // Call this API to enumerate all of the AppContainers on the system
+ [DllImport("FirewallAPI.dll")]
+ internal static extern uint NetworkIsolationEnumAppContainers(uint Flags, out uint pdwCntPublicACs, out IntPtr ppACs);
+ // DWORD NetworkIsolationEnumAppContainers(
+ // _In_ DWORD Flags,
+ // _Out_ DWORD *pdwNumPublicAppCs,
+ // _Out_ PINET_FIREWALL_APP_CONTAINER *ppPublicAppCs
+ //);
+
+ //http://msdn.microsoft.com/en-gb/library/windows/desktop/hh968116.aspx
+ enum NETISO_FLAG
+ {
+ NETISO_FLAG_FORCE_COMPUTE_BINARIES = 0x1,
+ NETISO_FLAG_MAX = 0x2
+ }
+
+
+ public class AppContainer
+ {
+ public String appContainerName { get; set; }
+ public String displayName { get; set; }
+ public String workingDirectory { get; set; }
+ public String StringSid { get; set; }
+ public List<uint> capabilities { get; set; }
+ public bool LoopUtil { get; set; }
+
+ public AppContainer(String _appContainerName, String _displayName, String _workingDirectory, IntPtr _sid)
+ {
+ this.appContainerName = _appContainerName;
+ this.displayName = _displayName;
+ this.workingDirectory = _workingDirectory;
+ String tempSid;
+ ConvertSidToStringSid(_sid, out tempSid);
+ this.StringSid = tempSid;
+ }
+ }
+
+ internal List<LoopUtil.INET_FIREWALL_APP_CONTAINER> _AppList;
+ internal List<LoopUtil.SID_AND_ATTRIBUTES> _AppListConfig;
+ public List<AppContainer> Apps = new List<AppContainer>();
+ internal IntPtr _pACs;
+
+ public LoopUtil()
+ {
+ LoadApps();
+ }
+
+ public void LoadApps()
+ {
+ Apps.Clear();
+ _pACs = IntPtr.Zero;
+ //Full List of Apps
+ _AppList = PI_NetworkIsolationEnumAppContainers();
+ //List of Apps that have LoopUtil enabled.
+ _AppListConfig = PI_NetworkIsolationGetAppContainerConfig();
+ foreach (var PI_app in _AppList)
+ {
+ AppContainer app = new AppContainer(PI_app.appContainerName, PI_app.displayName, PI_app.workingDirectory, PI_app.appContainerSid);
+
+ var app_capabilities = LoopUtil.getCapabilites(PI_app.capabilities);
+ if (app_capabilities.Count > 0)
+ {
+ //var sid = new SecurityIdentifier(app_capabilities[0], 0);
+
+ IntPtr arrayValue = IntPtr.Zero;
+ //var b = LoopUtil.ConvertStringSidToSid(app_capabilities[0].Sid, out arrayValue);
+ //string mysid;
+ //var b = LoopUtil.ConvertSidToStringSid(app_capabilities[0].Sid, out mysid);
+ }
+ app.LoopUtil = CheckLoopback(PI_app.appContainerSid);
+ Apps.Add(app);
+ }
+ }
+ private bool CheckLoopback(IntPtr intPtr)
+ {
+ foreach (SID_AND_ATTRIBUTES item in _AppListConfig)
+ {
+ string left, right;
+ ConvertSidToStringSid(item.Sid, out left);
+ ConvertSidToStringSid(intPtr, out right);
+
+ if (left == right)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private bool CreateExcemptions(string appName)
+ {
+ var hasChanges = false;
+
+ foreach (var app in Apps)
+ {
+ if ((app.appContainerName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1 ||
+ (app.displayName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ if (!app.LoopUtil)
+ {
+ app.LoopUtil = true;
+ hasChanges = true;
+ }
+ }
+ }
+
+ return hasChanges;
+ }
+
+ public static void Run(string appName)
+ {
+ var util = new LoopUtil();
+ util.LoadApps();
+
+ var hasChanges = util.CreateExcemptions(appName);
+
+ if (hasChanges)
+ {
+ util.SaveLoopbackState();
+ }
+ util.SaveLoopbackState();
+ }
+
+ private static List<SID_AND_ATTRIBUTES> getCapabilites(INET_FIREWALL_AC_CAPABILITIES cap)
+ {
+ List<SID_AND_ATTRIBUTES> mycap = new List<SID_AND_ATTRIBUTES>();
+
+ IntPtr arrayValue = cap.capabilities;
+
+ var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
+ for (var i = 0; i < cap.count; i++)
+ {
+ var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES));
+ mycap.Add(cur);
+ arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize));
+ }
+
+ return mycap;
+
+ }
+
+ private static List<SID_AND_ATTRIBUTES> getContainerSID(INET_FIREWALL_AC_CAPABILITIES cap)
+ {
+ List<SID_AND_ATTRIBUTES> mycap = new List<SID_AND_ATTRIBUTES>();
+
+ IntPtr arrayValue = cap.capabilities;
+
+ var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
+ for (var i = 0; i < cap.count; i++)
+ {
+ var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES));
+ mycap.Add(cur);
+ arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize));
+ }
+
+ return mycap;
+
+ }
+
+ private static List<SID_AND_ATTRIBUTES> PI_NetworkIsolationGetAppContainerConfig()
+ {
+
+ IntPtr arrayValue = IntPtr.Zero;
+ uint size = 0;
+ var list = new List<SID_AND_ATTRIBUTES>();
+
+ // Pin down variables
+ GCHandle handle_pdwCntPublicACs = GCHandle.Alloc(size, GCHandleType.Pinned);
+ GCHandle handle_ppACs = GCHandle.Alloc(arrayValue, GCHandleType.Pinned);
+
+ uint retval = NetworkIsolationGetAppContainerConfig(out size, out arrayValue);
+
+ var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
+ for (var i = 0; i < size; i++)
+ {
+ var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES));
+ list.Add(cur);
+ arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize));
+ }
+
+ //release pinned variables.
+ handle_pdwCntPublicACs.Free();
+ handle_ppACs.Free();
+
+ return list;
+
+
+ }
+
+ private List<INET_FIREWALL_APP_CONTAINER> PI_NetworkIsolationEnumAppContainers()
+ {
+
+ IntPtr arrayValue = IntPtr.Zero;
+ uint size = 0;
+ var list = new List<INET_FIREWALL_APP_CONTAINER>();
+
+ // Pin down variables
+ GCHandle handle_pdwCntPublicACs = GCHandle.Alloc(size, GCHandleType.Pinned);
+ GCHandle handle_ppACs = GCHandle.Alloc(arrayValue, GCHandleType.Pinned);
+
+ //uint retval2 = NetworkIsolationGetAppContainerConfig( out size, out arrayValue);
+
+ uint retval = NetworkIsolationEnumAppContainers((Int32)NETISO_FLAG.NETISO_FLAG_MAX, out size, out arrayValue);
+ _pACs = arrayValue; //store the pointer so it can be freed when we close the form
+
+ var structSize = Marshal.SizeOf(typeof(INET_FIREWALL_APP_CONTAINER));
+ for (var i = 0; i < size; i++)
+ {
+ var cur = (INET_FIREWALL_APP_CONTAINER)Marshal.PtrToStructure(arrayValue, typeof(INET_FIREWALL_APP_CONTAINER));
+ list.Add(cur);
+ arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize));
+ }
+
+ //release pinned variables.
+ handle_pdwCntPublicACs.Free();
+ handle_ppACs.Free();
+
+ return list;
+
+
+ }
+
+ public bool SaveLoopbackState()
+ {
+ var countEnabled = CountEnabledLoopUtil();
+ SID_AND_ATTRIBUTES[] arr = new SID_AND_ATTRIBUTES[countEnabled];
+ int count = 0;
+
+ for (int i = 0; i < Apps.Count; i++)
+ {
+ if (Apps[i].LoopUtil)
+ {
+ arr[count].Attributes = 0;
+ //TO DO:
+ IntPtr ptr;
+ ConvertStringSidToSid(Apps[i].StringSid, out ptr);
+ arr[count].Sid = ptr;
+ count++;
+ }
+
+ }
+
+
+ if (NetworkIsolationSetAppContainerConfig((uint)countEnabled, arr) == 0)
+ {
+ return true;
+ }
+ else
+ { return false; }
+
+ }
+
+ private int CountEnabledLoopUtil()
+ {
+ var count = 0;
+ for (int i = 0; i < Apps.Count; i++)
+ {
+ if (Apps[i].LoopUtil)
+ {
+ count++;
+ }
+
+ }
+ return count;
+ }
+
+ public void FreeResources()
+ {
+ NetworkIsolationFreeAppContainers(_pACs);
+ }
+
+ }
+}
diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config
index 7e638e171..ccabbc27b 100644
--- a/Emby.Server.Implementations/packages.config
+++ b/Emby.Server.Implementations/packages.config
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Emby.XmlTv" version="1.0.7" targetFramework="portable45-net45+win8" />
- <package id="MediaBrowser.Naming" version="1.0.4" targetFramework="portable45-net45+win8" />
+ <package id="MediaBrowser.Naming" version="1.0.5" targetFramework="portable45-net45+win8" />
<package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="portable45-net45+win8" />
<package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" />