diff --git a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs
similarity index 99%
rename from MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs
rename to Emby.Common.Implementations/IO/LnkShortcutHandler.cs
index b4a87b9b4..5d5f46057 100644
--- a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs
+++ b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs
@@ -6,7 +6,7 @@ using System.Security;
using System.Text;
using MediaBrowser.Model.IO;
-namespace MediaBrowser.ServerApplication.Native
+namespace Emby.Common.Implementations.IO
{
public class LnkShortcutHandler :IShortcutHandler
{
@@ -35,7 +35,6 @@ namespace MediaBrowser.ServerApplication.Native
///
/// Class NativeMethods
///
- [SuppressUnmanagedCodeSecurity]
public static class NativeMethods
{
///
diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
index 3fe20f659..0c1c02cd5 100644
--- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.System;
namespace Emby.Common.Implementations.IO
{
@@ -18,17 +19,21 @@ namespace Emby.Common.Implementations.IO
private readonly bool _supportsAsyncFileStreams;
private char[] _invalidFileNameChars;
private readonly List _shortcutHandlers = new List();
- private bool EnableFileSystemRequestConcat = true;
+ private bool EnableFileSystemRequestConcat;
private string _tempPath;
- public ManagedFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, bool enableFileSystemRequestConcat, string tempPath)
+ public ManagedFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath)
{
Logger = logger;
- _supportsAsyncFileStreams = supportsAsyncFileStreams;
+ _supportsAsyncFileStreams = true;
_tempPath = tempPath;
- EnableFileSystemRequestConcat = enableFileSystemRequestConcat;
- SetInvalidFileNameChars(enableManagedInvalidFileNameChars);
+
+ // On Linux, this needs to be true or symbolic links are ignored
+ EnableFileSystemRequestConcat = environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows &&
+ environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.OSX;
+
+ SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
}
public void AddShortcutHandler(IShortcutHandler handler)
diff --git a/Emby.Common.Implementations/Net/NetAcceptSocket.cs b/Emby.Common.Implementations/Net/NetAcceptSocket.cs
index 731ad1b74..3721709e6 100644
--- a/Emby.Common.Implementations/Net/NetAcceptSocket.cs
+++ b/Emby.Common.Implementations/Net/NetAcceptSocket.cs
@@ -2,6 +2,7 @@
using System.Net;
using System.Net.Sockets;
using System.Threading;
+using System.Threading.Tasks;
using Emby.Common.Implementations.Networking;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Logging;
@@ -59,7 +60,7 @@ namespace Emby.Common.Implementations.Net
#if NET46
Socket.Close();
#else
- Socket.Dispose();
+ Socket.Dispose();
#endif
}
@@ -96,6 +97,46 @@ namespace Emby.Common.Implementations.Net
_acceptor.StartAccept();
}
+#if NET46
+ public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
+ {
+ var options = TransmitFileOptions.UseKernelApc;
+
+ var completionSource = new TaskCompletionSource();
+
+ var result = Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), new Tuple>(Socket, path, completionSource));
+
+ return completionSource.Task;
+ }
+
+ private void FileSendCallback(IAsyncResult ar)
+ {
+ // Retrieve the socket from the state object.
+ Tuple> data = (Tuple>)ar.AsyncState;
+
+ var client = data.Item1;
+ var path = data.Item2;
+ var taskCompletion = data.Item3;
+
+ // Complete sending the data to the remote device.
+ try {
+ client.EndSendFile(ar);
+ taskCompletion.TrySetResult(true);
+}
+ catch(SocketException ex){
+ _logger.Info("Socket.SendFile failed for {0}. error code {1}", path, ex.SocketErrorCode);
+ taskCompletion.TrySetException(ex);
+}catch(Exception ex){
+ taskCompletion.TrySetException(ex);
+}
+ }
+#else
+ public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+#endif
+
public void Dispose()
{
Socket.Dispose();
diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs
index 523f4da85..1fd367afb 100644
--- a/Emby.Common.Implementations/Net/SocketFactory.cs
+++ b/Emby.Common.Implementations/Net/SocketFactory.cs
@@ -52,6 +52,18 @@ namespace Emby.Common.Implementations.Net
{
throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex);
}
+ catch (ArgumentException ex)
+ {
+ if (dualMode)
+ {
+ // Mono for BSD incorrectly throws ArgumentException instead of SocketException
+ throw new SocketCreateException("AddressFamilyNotSupported", ex);
+ }
+ else
+ {
+ throw;
+ }
+ }
}
public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort)
@@ -59,6 +71,7 @@ namespace Emby.Common.Implementations.Net
if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", "remotePort");
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
+
try
{
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
@@ -96,10 +109,31 @@ namespace Emby.Common.Implementations.Net
}
}
+ public ISocket CreateUdpBroadcastSocket(int localPort)
+ {
+ if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
+
+ var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+ try
+ {
+ retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
+
+ return new UdpSocket(retVal, localPort, IPAddress.Any);
+ }
+ catch
+ {
+ if (retVal != null)
+ retVal.Dispose();
+
+ throw;
+ }
+ }
+
///
- /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
- ///
- /// An implementation of the interface used by RSSDP components to perform acceptSocket operations.
+ /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
+ ///
+ /// An implementation of the interface used by RSSDP components to perform acceptSocket operations.
public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
{
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs
index 8e2a1da6f..f9181eb6a 100644
--- a/Emby.Common.Implementations/Net/UdpSocket.cs
+++ b/Emby.Common.Implementations/Net/UdpSocket.cs
@@ -16,12 +16,23 @@ namespace Emby.Common.Implementations.Net
internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket
{
-
- #region Fields
-
private Socket _Socket;
private int _LocalPort;
- #endregion
+
+ private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
+ {
+ SocketFlags = SocketFlags.None
+ };
+
+ private readonly SocketAsyncEventArgs _sendSocketAsyncEventArgs = new SocketAsyncEventArgs()
+ {
+ SocketFlags = SocketFlags.None
+ };
+
+ private TaskCompletionSource _currentReceiveTaskCompletionSource;
+ private TaskCompletionSource _currentSendTaskCompletionSource;
+
+ private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
public UdpSocket(Socket socket, int localPort, IPAddress ip)
{
@@ -32,6 +43,61 @@ namespace Emby.Common.Implementations.Net
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
_Socket.Bind(new IPEndPoint(ip, _LocalPort));
+
+ InitReceiveSocketAsyncEventArgs();
+ }
+
+ private void InitReceiveSocketAsyncEventArgs()
+ {
+ var receiveBuffer = new byte[8192];
+ _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
+ _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed;
+
+ var sendBuffer = new byte[8192];
+ _sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length);
+ _sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed;
+ }
+
+ private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
+ {
+ var tcs = _currentReceiveTaskCompletionSource;
+ if (tcs != null)
+ {
+ _currentReceiveTaskCompletionSource = null;
+
+ if (e.SocketError == SocketError.Success)
+ {
+ tcs.TrySetResult(new SocketReceiveResult
+ {
+ Buffer = e.Buffer,
+ ReceivedBytes = e.BytesTransferred,
+ RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint),
+ LocalIPAddress = LocalIPAddress
+ });
+ }
+ else
+ {
+ tcs.TrySetException(new Exception("SocketError: " + e.SocketError));
+ }
+ }
+ }
+
+ private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
+ {
+ var tcs = _currentSendTaskCompletionSource;
+ if (tcs != null)
+ {
+ _currentSendTaskCompletionSource = null;
+
+ if (e.SocketError == SocketError.Success)
+ {
+ tcs.TrySetResult(e.BytesTransferred);
+ }
+ else
+ {
+ tcs.TrySetException(new Exception("SocketError: " + e.SocketError));
+ }
+ }
}
public UdpSocket(Socket socket, IpEndPointInfo endPoint)
@@ -40,6 +106,8 @@ namespace Emby.Common.Implementations.Net
_Socket = socket;
_Socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
+
+ InitReceiveSocketAsyncEventArgs();
}
public IpAddressInfo LocalIPAddress
@@ -48,32 +116,33 @@ namespace Emby.Common.Implementations.Net
private set;
}
- #region ISocket Members
-
public Task ReceiveAsync(CancellationToken cancellationToken)
{
ThrowIfDisposed();
-
var tcs = new TaskCompletionSource();
-
EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0);
+
var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
state.TaskCompletionSource = tcs;
-#if NETSTANDARD1_6
- _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
- .ContinueWith((task, asyncState) =>
+ cancellationToken.Register(() => tcs.TrySetCanceled());
+
+ _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint;
+ _currentReceiveTaskCompletionSource = tcs;
+
+ try
+ {
+ var willRaiseEvent = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs);
+
+ if (!willRaiseEvent)
{
- if (task.Status != TaskStatus.Faulted)
- {
- var receiveState = asyncState as AsyncReceiveState;
- receiveState.RemoteEndPoint = task.Result.RemoteEndPoint;
- ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress);
- }
- }, state);
-#else
- _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state);
-#endif
+ _receiveSocketAsyncEventArgs_Completed(this, _receiveSocketAsyncEventArgs);
+ }
+ }
+ catch (Exception ex)
+ {
+ tcs.TrySetException(ex);
+ }
return tcs.Task;
}
@@ -129,15 +198,48 @@ namespace Emby.Common.Implementations.Net
taskSource.TrySetException(ex);
}
- //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port));
-
return taskSource.Task;
#endif
+ //ThrowIfDisposed();
+
+ //if (buffer == null) throw new ArgumentNullException("messageData");
+ //if (endPoint == null) throw new ArgumentNullException("endPoint");
+
+ //cancellationToken.ThrowIfCancellationRequested();
+
+ //var tcs = new TaskCompletionSource();
+
+ //cancellationToken.Register(() => tcs.TrySetCanceled());
+
+ //_sendSocketAsyncEventArgs.SetBuffer(buffer, 0, size);
+ //_sendSocketAsyncEventArgs.RemoteEndPoint = NetworkManager.ToIPEndPoint(endPoint);
+ //_currentSendTaskCompletionSource = tcs;
+
+ //var willRaiseEvent = _Socket.SendAsync(_sendSocketAsyncEventArgs);
+
+ //if (!willRaiseEvent)
+ //{
+ // _sendSocketAsyncEventArgs_Completed(this, _sendSocketAsyncEventArgs);
+ //}
+
+ //return tcs.Task;
}
- #endregion
+ public async Task SendWithLockAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken)
+ {
+ ThrowIfDisposed();
- #region Overrides
+ //await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ await SendAsync(buffer, size, endPoint, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ //_sendLock.Release();
+ }
+ }
protected override void Dispose(bool disposing)
{
@@ -146,44 +248,19 @@ namespace Emby.Common.Implementations.Net
var socket = _Socket;
if (socket != null)
socket.Dispose();
- }
- }
- #endregion
+ _sendLock.Dispose();
- #region Private Methods
-
- private static void ProcessResponse(AsyncReceiveState state, Func receiveData, IpAddressInfo localIpAddress)
- {
- try
- {
- var bytesRead = receiveData();
-
- var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
- state.TaskCompletionSource.SetResult(
- new SocketReceiveResult
- {
- Buffer = state.Buffer,
- ReceivedBytes = bytesRead,
- RemoteEndPoint = ToIpEndPointInfo(ipEndPoint),
- LocalIPAddress = localIpAddress
- }
- );
- }
- catch (ObjectDisposedException)
- {
- state.TaskCompletionSource.SetCanceled();
- }
- catch (SocketException se)
- {
- if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown)
- state.TaskCompletionSource.SetException(se);
- else
- state.TaskCompletionSource.SetCanceled();
- }
- catch (Exception ex)
- {
- state.TaskCompletionSource.SetException(ex);
+ var tcs = _currentReceiveTaskCompletionSource;
+ if (tcs != null)
+ {
+ tcs.TrySetCanceled();
+ }
+ var sendTcs = _currentSendTaskCompletionSource;
+ if (sendTcs != null)
+ {
+ sendTcs.TrySetCanceled();
+ }
}
}
@@ -227,10 +304,6 @@ namespace Emby.Common.Implementations.Net
#endif
}
- #endregion
-
- #region Private Classes
-
private class AsyncReceiveState
{
public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint)
@@ -247,8 +320,5 @@ namespace Emby.Common.Implementations.Net
public TaskCompletionSource TaskCompletionSource { get; set; }
}
-
- #endregion
-
}
}
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index d048dcde1..c7eae91dd 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -208,7 +208,7 @@ namespace Emby.Dlna.Didl
var targetHeight = streamInfo.TargetHeight;
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
- streamInfo.VideoCodec,
+ streamInfo.TargetVideoCodec,
streamInfo.TargetAudioCodec,
targetWidth,
targetHeight,
@@ -352,7 +352,7 @@ namespace Emby.Dlna.Didl
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
streamInfo.TargetAudioCodec,
- streamInfo.VideoCodec,
+ streamInfo.TargetVideoCodec,
streamInfo.TargetAudioBitrate,
targetWidth,
targetHeight,
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index 3c07e95db..b73332c4b 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -542,7 +542,7 @@ namespace Emby.Dlna.PlayTo
{
var list = new ContentFeatureBuilder(profile)
.BuildVideoHeader(streamInfo.Container,
- streamInfo.VideoCodec,
+ streamInfo.TargetVideoCodec,
streamInfo.TargetAudioCodec,
streamInfo.TargetWidth,
streamInfo.TargetHeight,
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index a15f75c9a..5b04ceea2 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -238,7 +238,7 @@ namespace Emby.Drawing
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
- var imageProcessingLockTaken = false;
+ //var imageProcessingLockTaken = false;
try
{
@@ -253,9 +253,9 @@ namespace Emby.Drawing
var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
- await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
+ //await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
- imageProcessingLockTaken = true;
+ //imageProcessingLockTaken = true;
_imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
CopyFile(tmpPath, cacheFilePath);
@@ -273,13 +273,13 @@ namespace Emby.Drawing
// Just spit out the original file if all the options are default
return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
- finally
- {
- if (imageProcessingLockTaken)
- {
- _imageProcessingSemaphore.Release();
- }
- }
+ //finally
+ //{
+ // if (imageProcessingLockTaken)
+ // {
+ // _imageProcessingSemaphore.Release();
+ // }
+ //}
}
private void CopyFile(string src, string destination)
diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs
index 4425d1a0b..50c572b8c 100644
--- a/Emby.Server.Core/ApplicationHost.cs
+++ b/Emby.Server.Core/ApplicationHost.cs
@@ -61,7 +61,7 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Common.Implementations;
using Emby.Common.Implementations.Archiving;
-using Emby.Common.Implementations.Networking;
+using Emby.Common.Implementations.IO;
using Emby.Common.Implementations.Reflection;
using Emby.Common.Implementations.Serialization;
using Emby.Common.Implementations.TextEncoding;
@@ -93,7 +93,7 @@ using Emby.Server.Implementations.Social;
using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections;
using Emby.Server.Implementations.Dto;
-using Emby.Server.Implementations.EntryPoints;
+using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.FileOrganization;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.Security;
@@ -107,7 +107,6 @@ using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations;
using Emby.Server.Implementations.ServerManager;
using Emby.Server.Implementations.Session;
-using Emby.Server.Implementations.Social;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
using MediaBrowser.Model.Activity;
@@ -294,6 +293,13 @@ namespace Emby.Server.Core
ImageEncoder = imageEncoder;
SetBaseExceptionMessage();
+
+ if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
+ {
+ fileSystem.AddShortcutHandler(new LnkShortcutHandler());
+ }
+
+ fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
}
private Version _version;
@@ -606,7 +612,7 @@ namespace Emby.Server.Core
CertificatePath = GetCertificatePath(true);
Certificate = GetCertificate(CertificatePath);
- HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, SupportsDualModeSockets);
+ HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, FileSystemManager, SupportsDualModeSockets);
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
RegisterSingleInstance(HttpServer, false);
progress.Report(10);
@@ -796,17 +802,25 @@ namespace Emby.Server.Core
info.FFMpegFilename = "ffmpeg";
info.FFProbeFilename = "ffprobe";
info.ArchiveType = "7z";
- info.Version = "20160215";
+ info.Version = "20170308";
info.DownloadUrls = GetLinuxDownloadUrls();
}
else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
{
info.FFMpegFilename = "ffmpeg.exe";
info.FFProbeFilename = "ffprobe.exe";
- info.Version = "20160410";
+ info.Version = "20170308";
info.ArchiveType = "7z";
info.DownloadUrls = GetWindowsDownloadUrls();
}
+ else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
+ {
+ info.FFMpegFilename = "ffmpeg";
+ info.FFProbeFilename = "ffprobe";
+ info.ArchiveType = "7z";
+ info.Version = "20170308";
+ info.DownloadUrls = GetMacDownloadUrls();
+ }
else
{
// No version available - user requirement
@@ -816,6 +830,20 @@ namespace Emby.Server.Core
return info;
}
+ private string[] GetMacDownloadUrls()
+ {
+ switch (EnvironmentInfo.SystemArchitecture)
+ {
+ case Architecture.X64:
+ return new[]
+ {
+ "https://embydata.com/downloads/ffmpeg/osx/ffmpeg-x64-20170308.7z"
+ };
+ }
+
+ return new string[] { };
+ }
+
private string[] GetWindowsDownloadUrls()
{
switch (EnvironmentInfo.SystemArchitecture)
@@ -823,12 +851,12 @@ namespace Emby.Server.Core
case Architecture.X64:
return new[]
{
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win64.7z"
+ "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win64.7z"
};
case Architecture.X86:
return new[]
{
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win32.7z"
+ "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win32.7z"
};
}
@@ -842,12 +870,12 @@ namespace Emby.Server.Core
case Architecture.X64:
return new[]
{
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z"
+ "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-64bit-static.7z"
};
case Architecture.X86:
return new[]
{
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z"
+ "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-32bit-static.7z"
};
}
@@ -1714,14 +1742,8 @@ namespace Emby.Server.Core
((IProcess)sender).Dispose();
}
- public void EnableLoopback(string appName)
+ public virtual void EnableLoopback(string appName)
{
- EnableLoopbackInternal(appName);
- }
-
- protected virtual void EnableLoopbackInternal(string appName)
- {
-
}
private void RegisterModules()
diff --git a/Emby.Server.Core/HttpServerFactory.cs b/Emby.Server.Core/HttpServerFactory.cs
index deed3c6f3..dfd435c33 100644
--- a/Emby.Server.Core/HttpServerFactory.cs
+++ b/Emby.Server.Core/HttpServerFactory.cs
@@ -45,6 +45,7 @@ namespace Emby.Server.Core
IXmlSerializer xml,
IEnvironmentInfo environment,
ICertificate certificate,
+ IFileSystem fileSystem,
bool enableDualModeSockets)
{
var logger = logManager.GetLogger("HttpServer");
@@ -65,7 +66,8 @@ namespace Emby.Server.Core
certificate,
new StreamFactory(),
GetParseFn,
- enableDualModeSockets);
+ enableDualModeSockets,
+ fileSystem);
}
private static Func GetParseFn(Type propertyType)
diff --git a/Emby.Server.Core/IO/LibraryMonitor.cs b/Emby.Server.Core/IO/LibraryMonitor.cs
index 4df9b930e..e1e3186c3 100644
--- a/Emby.Server.Core/IO/LibraryMonitor.cs
+++ b/Emby.Server.Core/IO/LibraryMonitor.cs
@@ -421,17 +421,6 @@ namespace Emby.Server.Core.IO
var path = e.FullPath;
- // For deletes, use the parent path
- if (e.ChangeType == WatcherChangeTypes.Deleted)
- {
- var parentPath = Path.GetDirectoryName(path);
-
- if (!string.IsNullOrWhiteSpace(parentPath))
- {
- path = parentPath;
- }
- }
-
ReportFileSystemChanged(path);
}
catch (Exception ex)
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/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index e65ebeb04..c5ba6c892 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -3204,6 +3204,40 @@ namespace Emby.Server.Implementations.Data
}
}
+ private bool IsAlphaNumeric(string str)
+ {
+ if (string.IsNullOrWhiteSpace(str))
+ return false;
+
+ for (int i = 0; i < str.Length; i++)
+ {
+ if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool IsValidType(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+
+ private bool IsValidMediaType(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+
+ private bool IsValidId(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+
+ private bool IsValidPersonType(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+
private List GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
{
if (query.IsResumable ?? false)
@@ -3423,9 +3457,9 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@ChannelId", query.ChannelIds[0]);
}
}
- if (query.ChannelIds.Length > 1)
+ else if (query.ChannelIds.Length > 1)
{
- var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i + "'").ToArray());
+ var inClause = string.Join(",", query.ChannelIds.Where(IsValidId).Select(i => "'" + i + "'").ToArray());
whereClauses.Add(string.Format("ChannelId in ({0})", inClause));
}
@@ -4157,17 +4191,18 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))");
}
}
- if (query.MediaTypes.Length == 1)
+ var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray();
+ if (queryMediaTypes.Length == 1)
{
whereClauses.Add("MediaType=@MediaTypes");
if (statement != null)
{
- statement.TryBind("@MediaTypes", query.MediaTypes[0]);
+ statement.TryBind("@MediaTypes", queryMediaTypes[0]);
}
}
- if (query.MediaTypes.Length > 1)
+ else if (queryMediaTypes.Length > 1)
{
- var val = string.Join(",", query.MediaTypes.Select(i => "'" + i + "'").ToArray());
+ var val = string.Join(",", queryMediaTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("MediaType in (" + val + ")");
}
@@ -4273,7 +4308,9 @@ namespace Emby.Server.Implementations.Data
//var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0;
var enableItemsByName = query.IncludeItemsByName ?? false;
- if (query.TopParentIds.Length == 1)
+ var queryTopParentIds = query.TopParentIds.Where(IsValidId).ToArray();
+
+ if (queryTopParentIds.Length == 1)
{
if (enableItemsByName)
{
@@ -4289,12 +4326,12 @@ namespace Emby.Server.Implementations.Data
}
if (statement != null)
{
- statement.TryBind("@TopParentId", query.TopParentIds[0]);
+ statement.TryBind("@TopParentId", queryTopParentIds[0]);
}
}
- if (query.TopParentIds.Length > 1)
+ else if (queryTopParentIds.Length > 1)
{
- var val = string.Join(",", query.TopParentIds.Select(i => "'" + i + "'").ToArray());
+ var val = string.Join(",", queryTopParentIds.Select(i => "'" + i + "'").ToArray());
if (enableItemsByName)
{
@@ -4544,7 +4581,7 @@ namespace Emby.Server.Implementations.Data
return result;
}
- return new[] { value };
+ return new[] { value }.Where(IsValidType);
}
public async Task DeleteItem(Guid id, CancellationToken cancellationToken)
@@ -4696,31 +4733,35 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidParamValue());
}
}
- if (query.PersonTypes.Count == 1)
+ var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
+
+ if (queryPersonTypes.Count == 1)
{
whereClauses.Add("PersonType=@PersonType");
if (statement != null)
{
- statement.TryBind("@PersonType", query.PersonTypes[0]);
+ statement.TryBind("@PersonType", queryPersonTypes[0]);
}
}
- if (query.PersonTypes.Count > 1)
+ else if (queryPersonTypes.Count > 1)
{
- var val = string.Join(",", query.PersonTypes.Select(i => "'" + i + "'").ToArray());
+ var val = string.Join(",", queryPersonTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("PersonType in (" + val + ")");
}
- if (query.ExcludePersonTypes.Count == 1)
+ var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList();
+
+ if (queryExcludePersonTypes.Count == 1)
{
whereClauses.Add("PersonType<>@PersonType");
if (statement != null)
{
- statement.TryBind("@PersonType", query.ExcludePersonTypes[0]);
+ statement.TryBind("@PersonType", queryExcludePersonTypes[0]);
}
}
- if (query.ExcludePersonTypes.Count > 1)
+ else if (queryExcludePersonTypes.Count > 1)
{
- var val = string.Join(",", query.ExcludePersonTypes.Select(i => "'" + i + "'").ToArray());
+ var val = string.Join(",", queryExcludePersonTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("PersonType not in (" + val + ")");
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 147abd171..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) || fields.Contains(ItemFields.PlayAccess))
+ 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..670acd37f 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -84,7 +84,7 @@
-
+
@@ -102,7 +102,6 @@
-
@@ -170,7 +169,6 @@
-
@@ -302,8 +300,8 @@
..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll
True
-
- ..\packages\MediaBrowser.Naming.1.0.4\lib\portable-net45+win8\MediaBrowser.Naming.dll
+
+ ..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll
True
diff --git a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
index f841b8b6b..0a9c67285 100644
--- a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
+++ b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
@@ -677,20 +677,7 @@ namespace Emby.Server.Implementations.FileOrganization
var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options);
- // MAX_PATH - trailing charachter - drive component: 260 - 1 - 3 = 256
- // Usually newPath would include the drive component, but use 256 to be sure
- var maxFilenameLength = 256 - newPath.Length;
-
- if (!newPath.EndsWith(@"\"))
- {
- // Remove 1 for missing backslash combining path and filename
- maxFilenameLength--;
- }
-
- // Remove additional 4 chars to prevent PathTooLongException for downloaded subtitles (eg. filename.ext.eng.srt)
- maxFilenameLength -= 4;
-
- var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options, maxFilenameLength);
+ var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options);
if (string.IsNullOrEmpty(episodeFileName))
{
@@ -742,7 +729,7 @@ namespace Emby.Server.Implementations.FileOrganization
return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName));
}
- private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options, int? maxLength)
+ private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options)
{
seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
@@ -786,32 +773,15 @@ namespace Emby.Server.Implementations.FileOrganization
.Replace("%0e", episodeNumber.ToString("00", _usCulture))
.Replace("%00e", episodeNumber.ToString("000", _usCulture));
- if (maxLength.HasValue && result.Contains("%#"))
+ if (result.Contains("%#"))
{
- // Substract 3 for the temp token length (%#1, %#2 or %#3)
- int maxRemainingTitleLength = maxLength.Value - result.Length + 3;
- string shortenedEpisodeTitle = string.Empty;
-
- if (maxRemainingTitleLength > 5)
- {
- // A title with fewer than 5 letters wouldn't be of much value
- shortenedEpisodeTitle = episodeTitle.Substring(0, Math.Min(maxRemainingTitleLength, episodeTitle.Length));
- }
-
- result = result.Replace("%#1", shortenedEpisodeTitle)
- .Replace("%#2", shortenedEpisodeTitle.Replace(" ", "."))
- .Replace("%#3", shortenedEpisodeTitle.Replace(" ", "_"));
+ result = result.Replace("%#1", episodeTitle)
+ .Replace("%#2", episodeTitle.Replace(" ", "."))
+ .Replace("%#3", episodeTitle.Replace(" ", "_"));
}
- if (maxLength.HasValue && result.Length > maxLength.Value)
- {
- // There may be cases where reducing the title length may still not be sufficient to
- // stay below maxLength
- var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength);
- throw new Exception(msg);
- }
-
- return result;
+ // Finally, call GetValidFilename again in case user customized the episode expression with any invalid filename characters
+ return _fileSystem.GetValidFilename(result).Trim();
}
private bool IsSameEpisode(string sourcePath, string newPath)
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
new file mode 100644
index 000000000..dbaf97b1e
--- /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 Cookies { get; private set; }
+
+ public FileShareMode FileShare { get; set; }
+
+ ///
+ /// The _options
+ ///
+ private readonly IDictionary _options = new Dictionary();
+ ///
+ /// Gets the options.
+ ///
+ /// The options.
+ public IDictionary 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();
+ }
+
+ ///
+ /// Sets the range values.
+ ///
+ 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);
+ }
+
+ ///
+ /// The _requested ranges
+ ///
+ private List> _requestedRanges;
+ ///
+ /// Gets the requested ranges.
+ ///
+ /// The requested ranges.
+ protected List> RequestedRanges
+ {
+ get
+ {
+ if (_requestedRanges == null)
+ {
+ _requestedRanges = new List>();
+
+ // 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(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, RangeLength, 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/GetSwaggerResource.cs b/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs
deleted file mode 100644
index 819ede1ab..000000000
--- a/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using MediaBrowser.Model.Services;
-
-namespace Emby.Server.Implementations.HttpServer
-{
- ///
- /// Class GetDashboardResource
- ///
- [Route("/swagger-ui/{ResourceName*}", "GET")]
- public class GetSwaggerResource
- {
- ///
- /// Gets or sets the name.
- ///
- /// The name.
- public string ResourceName { get; set; }
- }
-}
\ No newline at end of file
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index b5a3c2992..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> 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> 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)
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
};
}
-
- responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
-
- if (isHeadRequest)
+ else
{
- stream.Dispose();
+ var stream = await factoryFn().ConfigureAwait(false);
- return GetHttpResult(new byte[] { }, contentType, true);
+ responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
+
+ if (isHeadRequest)
+ {
+ stream.Dispose();
+
+ 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 _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 httpRequestFactory)
+ public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func 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 ErrorHandler { get; set; }
@@ -54,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void Start(IEnumerable 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/HttpServer/SwaggerService.cs b/Emby.Server.Implementations/HttpServer/SwaggerService.cs
deleted file mode 100644
index d41946645..000000000
--- a/Emby.Server.Implementations/HttpServer/SwaggerService.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Net;
-using System.IO;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Services;
-
-namespace Emby.Server.Implementations.HttpServer
-{
- public class SwaggerService : IService, IRequiresRequest
- {
- private readonly IServerApplicationPaths _appPaths;
- private readonly IFileSystem _fileSystem;
-
- public SwaggerService(IServerApplicationPaths appPaths, IFileSystem fileSystem, IHttpResultFactory resultFactory)
- {
- _appPaths = appPaths;
- _fileSystem = fileSystem;
- _resultFactory = resultFactory;
- }
-
- ///
- /// Gets the specified request.
- ///
- /// The request.
- /// System.Object.
- public object Get(GetSwaggerResource request)
- {
- var swaggerDirectory = Path.Combine(_appPaths.ApplicationResourcesPath, "swagger-ui");
-
- var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', _fileSystem.DirectorySeparatorChar));
-
- return _resultFactory.GetStaticFileResult(Request, requestedFile).Result;
- }
-
- ///
- /// Gets or sets the result factory.
- ///
- /// The result factory.
- private readonly IHttpResultFactory _resultFactory;
-
- ///
- /// Gets or sets the request context.
- ///
- /// The request context.
- public IRequest Request { get; set; }
- }
-}
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 f7706db47..026486efc 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -513,6 +513,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))
{
@@ -531,7 +536,7 @@ namespace Emby.Server.Implementations.Library
.Replace("/", "\\");
}
- if (!ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
+ if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
{
key = key.ToLower();
}
@@ -865,7 +870,7 @@ namespace Emby.Server.Implementations.Library
/// Task{Person}.
public Person GetPerson(string name)
{
- return CreateItemByName(Person.GetPath(name), name);
+ return CreateItemByName(Person.GetPath, name);
}
///
@@ -875,7 +880,7 @@ namespace Emby.Server.Implementations.Library
/// Task{Studio}.
public Studio GetStudio(string name)
{
- return CreateItemByName(Studio.GetPath(name), name);
+ return CreateItemByName(Studio.GetPath, name);
}
///
@@ -885,7 +890,7 @@ namespace Emby.Server.Implementations.Library
/// Task{Genre}.
public Genre GetGenre(string name)
{
- return CreateItemByName(Genre.GetPath(name), name);
+ return CreateItemByName(Genre.GetPath, name);
}
///
@@ -895,7 +900,7 @@ namespace Emby.Server.Implementations.Library
/// Task{MusicGenre}.
public MusicGenre GetMusicGenre(string name)
{
- return CreateItemByName(MusicGenre.GetPath(name), name);
+ return CreateItemByName(MusicGenre.GetPath, name);
}
///
@@ -905,7 +910,7 @@ namespace Emby.Server.Implementations.Library
/// Task{GameGenre}.
public GameGenre GetGameGenre(string name)
{
- return CreateItemByName(GameGenre.GetPath(name), name);
+ return CreateItemByName(GameGenre.GetPath, name);
}
///
@@ -923,7 +928,7 @@ namespace Emby.Server.Implementations.Library
var name = value.ToString(CultureInfo.InvariantCulture);
- return CreateItemByName(Year.GetPath(name), name);
+ return CreateItemByName(Year.GetPath, name);
}
///
@@ -933,10 +938,10 @@ namespace Emby.Server.Implementations.Library
/// Task{Genre}.
public MusicArtist GetArtist(string name)
{
- return CreateItemByName(MusicArtist.GetPath(name), name);
+ return CreateItemByName(MusicArtist.GetPath, name);
}
- private T CreateItemByName(string path, string name)
+ private T CreateItemByName(Func getPathFn, string name)
where T : BaseItem, new()
{
if (typeof(T) == typeof(MusicArtist))
@@ -957,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/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index c1bd8fe91..ccd4c3631 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -369,6 +369,8 @@ namespace Emby.Server.Implementations.Library
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+ enableAutoClose = false;
+
try
{
var tuple = GetProvider(request.OpenToken);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 926d82f94..0ea1b38b1 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -442,6 +442,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
+ foreach (var channel in result)
+ {
+ _logger.Info("Found epg channel in {0} {1} {2} {3}", provider.Name, info.ListingsId, channel.Name, channel.Id);
+ }
+
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
}
@@ -493,11 +498,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
{
- var mappedTunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings);
+ var tunerChannelId = tunerChannel.TunerChannelId;
+ if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
+ }
+
+ var mappedTunerChannelId = GetMappedChannel(tunerChannelId, mappings);
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
{
- mappedTunerChannelId = tunerChannel.TunerChannelId;
+ mappedTunerChannelId = tunerChannelId;
}
var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
@@ -639,8 +650,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
{
- var existingTimer = _timerProvider.GetAll()
- .FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase));
+ var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ?
+ null :
+ _timerProvider.GetTimerByProgramId(timer.ProgramId);
if (existingTimer != null)
{
@@ -710,7 +722,34 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new InvalidOperationException("SeriesId for program not found");
}
+ // If any timers have already been manually created, make sure they don't get cancelled
+ var existingTimers = (await GetTimersAsync(CancellationToken.None).ConfigureAwait(false))
+ .Where(i =>
+ {
+ if (string.Equals(i.ProgramId, info.ProgramId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.ProgramId))
+ {
+ return true;
+ }
+
+ if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId))
+ {
+ return true;
+ }
+
+ return false;
+ })
+ .ToList();
+
_seriesTimerProvider.Add(info);
+
+ foreach (var timer in existingTimers)
+ {
+ timer.SeriesTimerId = info.Id;
+ timer.IsManual = true;
+
+ _timerProvider.AddOrUpdate(timer, false);
+ }
+
await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false);
return info.Id;
@@ -991,6 +1030,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (epgChannel == null)
{
+ _logger.Debug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
programs = new List();
}
else
@@ -1276,6 +1316,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(),
@@ -2299,6 +2347,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var existingTimer = _timerProvider.GetTimer(timer.Id);
+ if (existingTimer == null)
+ {
+ existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId)
+ ? null
+ : _timerProvider.GetTimerByProgramId(timer.ProgramId);
+ }
+
if (existingTimer == null)
{
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
@@ -2313,12 +2368,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
else
{
- // Only update if not currently active
+ // Only update if not currently active - test both new timer and existing in case Id's are different
+ // Id's could be different if the timer was created manually prior to series timer creation
ActiveRecordingInfo activeRecordingInfo;
- if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo))
+ if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo) && !_activeRecordings.TryGetValue(existingTimer.Id, out activeRecordingInfo))
{
UpdateExistingTimerWithNewMetadata(existingTimer, timer);
+ // Needed by ShouldCancelTimerForSeriesTimer
+ timer.IsManual = existingTimer.IsManual;
+
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
{
existingTimer.Status = RecordingStatus.Cancelled;
@@ -2516,7 +2575,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
list.Add(new VirtualFolderInfo
{
Locations = new List { customPath },
- Name = "Recorded Series",
+ Name = "Recorded Shows",
CollectionType = CollectionType.TvShows
});
}
@@ -2531,6 +2590,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> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
+ {
+ var list = new List();
+
+ 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> 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();
+ }
+ }
}
public static class ConfigurationExtension
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 0567bdfd9..6cc5b6920 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
- var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4} -y \"{1}\"";
+ var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4}{6} -y \"{1}\"";
long startTimeTicks = 0;
//if (mediaSource.DateLiveStreamOpened.HasValue)
@@ -193,7 +193,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
- commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam);
+ var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
+ " -f mp4 -movflags frag_keyframe+empty_moov" :
+ string.Empty;
+
+ commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam, outputParam);
return inputModifiers + " " + commandLineArgs;
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 35868d318..2eec3df8a 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -166,5 +166,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
}
+
+ public TimerInfo GetTimerByProgramId(string programId)
+ {
+ return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
+ }
}
}
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/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 618cd1d45..b9e73b62e 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 GetTunerHostTypes()
+ {
+ return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
+ {
+ Name = i.Name,
+ Id = i.Type
+
+ }).ToList();
+ }
+
+ public Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
+ {
+ return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken);
+ }
+
void service_DataSourceChanged(object sender, EventArgs e)
{
if (!_isDisposed)
@@ -1063,25 +1078,28 @@ namespace Emby.Server.Implementations.LiveTv
var channel = GetInternalChannel(program.ChannelId);
- var channelUserdata = _userDataManager.GetUserData(userId, channel);
+ if (channel != null)
+ {
+ var channelUserdata = _userDataManager.GetUserData(userId, channel);
- if (channelUserdata.Likes ?? false)
- {
- score += 2;
- }
- else if (!(channelUserdata.Likes ?? true))
- {
- score -= 2;
- }
+ if (channelUserdata.Likes ?? false)
+ {
+ score += 2;
+ }
+ else if (!(channelUserdata.Likes ?? true))
+ {
+ score -= 2;
+ }
- if (channelUserdata.IsFavorite)
- {
- score += 3;
- }
+ if (channelUserdata.IsFavorite)
+ {
+ score += 3;
+ }
- if (factorChannelWatchCount)
- {
- score += channelUserdata.PlayCount;
+ if (factorChannelWatchCount)
+ {
+ score += channelUserdata.PlayCount;
+ }
}
return score;
@@ -1180,6 +1198,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
@@ -2748,7 +2768,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 GetEnabledUsers()
@@ -2986,7 +3006,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
@@ -3000,50 +3020,6 @@ namespace Emby.Server.Implementations.LiveTv
return _security.GetRegistrationStatus(feature);
}
- public List GetSatIniMappings()
- {
- return new List();
- //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> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
- {
- return Task.FromResult(new List());
- //return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
- }
-
public Task> 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..74b8a7764 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
var list = result.ToList();
- Logger.Debug("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
+ Logger.Info("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
if (!string.IsNullOrWhiteSpace(key) && list.Count > 0)
{
@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected virtual List 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 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 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(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("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 62385e172..8fa1bbe23 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -20,6 +20,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
@@ -30,8 +31,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
+ private readonly IEnvironmentInfo _environment;
- public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager)
+ public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
: base(config, logger, jsonSerializer, mediaEncoder)
{
_httpClient = httpClient;
@@ -39,6 +41,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
+ _environment = environment;
}
public string Name
@@ -56,7 +59,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)
{
@@ -125,7 +134,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
DiscoverResponse response;
if (_modelCache.TryGetValue(info.Url, out response))
{
- return response;
+ if ((DateTime.UtcNow - response.DateQueried).TotalHours <= 12)
+ {
+ return response;
+ }
}
}
@@ -135,8 +147,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CancellationToken = cancellationToken,
- CacheLength = TimeSpan.FromDays(1),
- CacheMode = CacheMode.Unconditional,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
BufferContent = false
@@ -215,7 +225,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var list = new List();
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
{
@@ -268,6 +278,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public int HD { get; set; }
}
+ protected EncodingOptions GetEncodingOptions()
+ {
+ return Config.GetConfiguration("encoding");
+ }
+
+ private string GetHdHrIdFromChannelId(string channelId)
+ {
+ return channelId.Split('_')[1];
+ }
+
private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, ChannelInfo channelInfo, string profile)
{
int? width = null;
@@ -355,14 +375,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
nal = "0";
}
- var url = GetApiUrl(info, true) + "/auto/v" + channelId;
-
- // If raw was used, the tuner doesn't support params
- if (!string.IsNullOrWhiteSpace(profile)
- && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
- {
- url += "?transcode=" + profile;
- }
+ var url = GetApiUrl(info, false);
var id = profile;
if (string.IsNullOrWhiteSpace(id))
@@ -371,92 +384,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
id += "_" + url.GetMD5().ToString("N");
- var mediaSource = new MediaSourceInfo
- {
- Path = url,
- Protocol = MediaProtocol.Http,
- MediaStreams = new List
- {
- new MediaStream
- {
- Type = MediaStreamType.Video,
- // Set the index to -1 because we don't know the exact index of the video stream within the container
- Index = -1,
- IsInterlaced = isInterlaced,
- Codec = videoCodec,
- Width = width,
- Height = height,
- BitRate = videoBitrate,
- NalLengthSize = nal
-
- },
- new MediaStream
- {
- Type = MediaStreamType.Audio,
- // Set the index to -1 because we don't know the exact index of the audio stream within the container
- Index = -1,
- Codec = audioCodec,
- BitRate = audioBitrate
- }
- },
- RequiresOpening = true,
- RequiresClosing = false,
- BufferMs = 0,
- Container = "ts",
- Id = id,
- SupportsDirectPlay = false,
- SupportsDirectStream = true,
- SupportsTranscoding = true,
- IsInfiniteStream = true
- };
-
- mediaSource.InferTotalBitrate();
-
- return mediaSource;
- }
-
- protected EncodingOptions GetEncodingOptions()
- {
- return Config.GetConfiguration("encoding");
- }
-
- private string GetHdHrIdFromChannelId(string channelId)
- {
- return channelId.Split('_')[1];
- }
-
- private MediaSourceInfo GetLegacyMediaSource(TunerHostInfo info, string channelId, ChannelInfo channel)
- {
- int? width = null;
- int? height = null;
- bool isInterlaced = true;
- string videoCodec = null;
- string audioCodec = null;
-
- int? videoBitrate = null;
- int? audioBitrate = null;
-
- if (channel != null)
- {
- if (string.IsNullOrWhiteSpace(videoCodec))
- {
- videoCodec = channel.VideoCodec;
- }
- audioCodec = channel.AudioCodec;
- }
-
- // normalize
- if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase))
- {
- videoCodec = "mpeg2video";
- }
-
- string nal = null;
-
- var url = GetApiUrl(info, false);
- var id = channelId;
- id += "_" + url.GetMD5().ToString("N");
-
var mediaSource = new MediaSourceInfo
{
Path = url,
@@ -520,7 +447,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (isLegacyTuner)
{
- list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo));
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
}
else
{
@@ -559,26 +486,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 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,30 +499,40 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
- if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
- {
- var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
- var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
+ var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
+ {
return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
}
- else
+
+ // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet
+ var enableHttpStream = _environment.OperatingSystem == OperatingSystem.OSX ||
+ _environment.OperatingSystem == OperatingSystem.BSD;
+ enableHttpStream = true;
+ if (enableHttpStream)
{
- var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
- //var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ mediaSource.Protocol = MediaProtocol.Http;
+
+ var httpUrl = GetApiUrl(info, true) + "/auto/v" + hdhrId;
+
+ // If raw was used, the tuner doesn't support params
+ if (!string.IsNullOrWhiteSpace(profile)
+ && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
+ {
+ httpUrl += "?transcode=" + profile;
+ }
+ mediaSource.Path = httpUrl;
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);
}
+
+ 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();
@@ -651,6 +574,83 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public string BaseURL { get; set; }
public string LineupURL { get; set; }
public int TunerCount { get; set; }
+
+ public DateTime DateQueried { get; set; }
+
+ public DiscoverResponse()
+ {
+ DateQueried = DateTime.UtcNow;
+ }
+ }
+
+ public async Task> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
+ {
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
+ var list = new List();
+
+ // 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 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/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index a881d0ea1..e1572ea3f 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -120,7 +120,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// send url to start streaming
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}", remoteAddress);
if (!cancellationToken.IsCancellationRequested)
@@ -131,12 +130,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
onStarted = () => openTaskCompletionSource.TrySetResult(true);
}
- var stream = new UdpClientStream(udpClient);
- await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false);
+ await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), onStarted, cancellationToken).ConfigureAwait(false);
}
}
- catch (OperationCanceledException)
+ catch (OperationCanceledException ex)
{
+ _logger.Info("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
+ openTaskCompletionSource.TrySetException(ex);
break;
}
catch (Exception ex)
@@ -155,7 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
await hdHomerunManager.StopStreaming().ConfigureAwait(false);
- udpClient.Dispose();
_liveStreamTaskCompletionSource.TrySetResult(true);
}
}
@@ -207,7 +206,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var data = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
-
+
// remove rtp header
Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead);
offset += bytesRead;
@@ -291,4 +290,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
throw new NotImplementedException();
}
}
-}
+}
\ No newline at end of file
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> 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> 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> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(new List());
+ }
}
}
\ 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 90ff36441..281632590 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
@@ -7,6 +7,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@@ -34,13 +35,73 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (bytesRead > 0)
{
- byte[] copy = new byte[bytesRead];
- Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
-
var allStreams = _outputStreams.ToList();
- foreach (var stream in allStreams)
+
+ if (allStreams.Count == 1)
{
- stream.Value.Queue(copy);
+ await allStreams[0].Value.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
+ }
+ else
+ {
+ byte[] copy = new byte[bytesRead];
+ Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
+
+ foreach (var stream in allStreams)
+ {
+ stream.Value.Queue(copy, 0, copy.Length);
+ }
+ }
+
+ if (onStarted != null)
+ {
+ var onStartedCopy = onStarted;
+ onStarted = null;
+ Task.Run(onStartedCopy);
+ }
+ }
+
+ else
+ {
+ await Task.Delay(100).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private static int RtpHeaderBytes = 12;
+ public async Task CopyUntilCancelled(ISocket udpClient, Action onStarted, CancellationToken cancellationToken)
+ {
+ _cancellationToken = cancellationToken;
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var receiveToken = cancellationToken;
+
+ // On the first connection attempt, put a timeout to avoid being stuck indefinitely in the event of failure
+ if (onStarted != null)
+ {
+ receiveToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token;
+ }
+
+ var data = await udpClient.ReceiveAsync(receiveToken).ConfigureAwait(false);
+ var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
+
+ if (bytesRead > 0)
+ {
+ var allStreams = _outputStreams.ToList();
+
+ if (allStreams.Count == 1)
+ {
+ await allStreams[0].Value.WriteAsync(data.Buffer, 0, bytesRead).ConfigureAwait(false);
+ }
+ else
+ {
+ byte[] copy = new byte[bytesRead];
+ Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead);
+
+ foreach (var stream in allStreams)
+ {
+ stream.Value.Queue(copy, 0, copy.Length);
+ }
}
if (onStarted != null)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
index 7b48ce21a..543d2e373 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class QueueStream
{
private readonly Stream _outputStream;
- private readonly ConcurrentQueue _queue = new ConcurrentQueue();
+ private readonly ConcurrentQueue> _queue = new ConcurrentQueue>();
private CancellationToken _cancellationToken;
public TaskCompletionSource TaskCompletion { get; private set; }
@@ -28,9 +28,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
TaskCompletion = new TaskCompletionSource();
}
- public void Queue(byte[] bytes)
+ public void Queue(byte[] bytes, int offset, int count)
{
- _queue.Enqueue(bytes);
+ _queue.Enqueue(new Tuple(bytes, offset, count));
}
public void Start(CancellationToken cancellationToken)
@@ -39,17 +39,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Task.Run(() => StartInternal());
}
- private byte[] Dequeue()
+ private Tuple Dequeue()
{
- byte[] bytes;
- if (_queue.TryDequeue(out bytes))
+ Tuple result;
+ if (_queue.TryDequeue(out result))
{
- return bytes;
+ return result;
}
return null;
}
+ private void OnClosed()
+ {
+ GC.Collect();
+ if (OnFinished != null)
+ {
+ OnFinished(this);
+ }
+ }
+
+ public async Task WriteAsync(byte[] bytes, int offset, int count)
+ {
+ //return _outputStream.WriteAsync(bytes, offset, count, cancellationToken);
+ var cancellationToken = _cancellationToken;
+
+ try
+ {
+ await _outputStream.WriteAsync(bytes, offset, count, cancellationToken).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.Debug("QueueStream cancelled");
+ TaskCompletion.TrySetCanceled();
+ OnClosed();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in QueueStream", ex);
+ TaskCompletion.TrySetException(ex);
+ OnClosed();
+ }
+ }
+
private async Task StartInternal()
{
var cancellationToken = _cancellationToken;
@@ -58,10 +90,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
while (true)
{
- var bytes = Dequeue();
- if (bytes != null)
+ var result = Dequeue();
+ if (result != null)
{
- await _outputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
+ await _outputStream.WriteAsync(result.Item1, result.Item2, result.Item3, cancellationToken).ConfigureAwait(false);
}
else
{
@@ -81,10 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
finally
{
- if (OnFinished != null)
- {
- OnFinished(this);
- }
+ OnClosed();
}
}
}
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
+ {
+ 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> 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 CreateImage(IHasImages item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ //{
+ // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
+ //}
+ }
+
}
diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs
index a26df7625..d42fae3ad 100644
--- a/Emby.Server.Implementations/Security/PluginSecurityManager.cs
+++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs
@@ -301,10 +301,12 @@ namespace Emby.Server.Implementations.Security
if (reg.registered)
{
+ _logger.Info("Registered for feature {0}", feature);
LicenseFile.AddRegCheck(feature, reg.expDate);
}
else
{
+ _logger.Info("Not registered for feature {0}", feature);
LicenseFile.RemoveRegCheck(feature);
}
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 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/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 9dfaa102a..b5e64bc23 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.TV
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items);
- return GetResult(episodes, null, request);
+ return GetResult(episodes, request);
}
public QueryResult GetNextUp(NextUpQuery request, List parentsFolders)
@@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.TV
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items);
- return GetResult(episodes, null, request);
+ return GetResult(episodes, request);
}
public IEnumerable GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable seriesKeys)
@@ -163,8 +163,7 @@ namespace Emby.Server.Implementations.TV
return false;
})
.Select(i => i.Item2())
- .Where(i => i != null)
- .Take(request.Limit ?? int.MaxValue);
+ .Where(i => i != null);
}
private string GetUniqueSeriesKey(BaseItem series)
@@ -232,24 +231,30 @@ namespace Emby.Server.Implementations.TV
return new Tuple>(DateTime.MinValue, getEpisode);
}
- private QueryResult GetResult(IEnumerable items, int? totalRecordLimit, NextUpQuery query)
+ private QueryResult GetResult(IEnumerable items, NextUpQuery query)
{
- var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
- var totalCount = itemsArray.Length;
+ int totalCount = 0;
+ if (query.EnableTotalRecordCount)
+ {
+ var list = items.ToList();
+ totalCount = list.Count;
+ items = list;
+ }
+
+ if (query.StartIndex.HasValue)
+ {
+ items = items.Skip(query.StartIndex.Value);
+ }
if (query.Limit.HasValue)
{
- itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray();
- }
- else if (query.StartIndex.HasValue)
- {
- itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray();
+ items = items.Take(query.Limit.Value);
}
return new QueryResult
{
TotalRecordCount = totalCount,
- Items = itemsArray
+ Items = items.ToArray()
};
}
}
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index bb303d8fa..21ef3cab6 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -203,19 +203,6 @@ namespace Emby.Server.Implementations.Udp
GC.SuppressFinalize(this);
}
- ///
- /// Stops this instance.
- ///
- public void Stop()
- {
- _isDisposed = true;
-
- if (_udpClient != null)
- {
- _udpClient.Dispose();
- }
- }
-
///
/// Releases unmanaged and - optionally - managed resources.
///
@@ -224,7 +211,12 @@ namespace Emby.Server.Implementations.Udp
{
if (dispose)
{
- Stop();
+ _isDisposed = true;
+
+ if (_udpClient != null)
+ {
+ _udpClient.Dispose();
+ }
}
}
@@ -247,9 +239,13 @@ namespace Emby.Server.Implementations.Udp
try
{
- await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
+ await _udpClient.SendWithLockAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
_logger.Info("Udp message sent to {0}", remoteEndPoint);
+ }
+ catch (OperationCanceledException)
+ {
+
}
catch (Exception ex)
{
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 @@
-
+
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index dc4e57155..2d8744f70 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -582,13 +582,13 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ListingProviders/Default", "GET")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class GetDefaultListingProvider : ListingsProviderInfo, IReturn
{
}
[Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class AddListingProvider : ListingsProviderInfo, IReturn
{
public bool ValidateLogin { get; set; }
@@ -596,7 +596,7 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class DeleteListingProvider : IReturnVoid
{
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
@@ -604,7 +604,7 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class GetLineups : IReturn>
{
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -621,13 +621,13 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class GetSchedulesDirectCountries
{
}
[Route("/LiveTv/ChannelMappingOptions")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class GetChannelMappingOptions
{
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -635,7 +635,7 @@ namespace MediaBrowser.Api.LiveTv
}
[Route("/LiveTv/ChannelMappings")]
- [Authenticated(AllowBeforeStartupWizard = true)]
+ [Authenticated]
public class SetChannelMapping
{
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@@ -660,20 +660,6 @@ namespace MediaBrowser.Api.LiveTv
public string Feature { get; set; }
}
- [Route("/LiveTv/TunerHosts/Satip/IniMappings", "GET", Summary = "Gets available mappings")]
- [Authenticated(AllowBeforeStartupWizard = true)]
- public class GetSatIniMappings : IReturn>
- {
-
- }
-
- [Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")]
- [Authenticated(AllowBeforeStartupWizard = true)]
- public class GetSatChannnelScanResult : TunerHostInfo
- {
-
- }
-
[Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")]
public class GetLiveStreamFile
{
@@ -687,6 +673,20 @@ namespace MediaBrowser.Api.LiveTv
public string Id { get; set; }
}
+ [Route("/LiveTv/TunerHosts/Types", "GET")]
+ [Authenticated]
+ public class GetTunerHostTypes : IReturn>
+ {
+
+ }
+
+ [Route("/LiveTv/Tuners/Discvover", "GET")]
+ [Authenticated]
+ public class DiscoverTuners : IReturn>
+ {
+ public bool NewDevicesOnly { get; set; }
+ }
+
public class LiveTvService : BaseApiService
{
private readonly ILiveTvManager _liveTvManager;
@@ -712,6 +712,12 @@ namespace MediaBrowser.Api.LiveTv
_sessionContext = sessionContext;
}
+ public object Get(GetTunerHostTypes request)
+ {
+ var list = _liveTvManager.GetTunerHostTypes();
+ return ToOptimizedResult(list);
+ }
+
public object Get(GetLiveRecordingFile request)
{
var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id);
@@ -731,6 +737,12 @@ namespace MediaBrowser.Api.LiveTv
};
}
+ public async Task