commit
23010f2980
|
@ -8,14 +8,14 @@ using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
namespace Emby.Common.Implementations.Net
|
namespace Emby.Common.Implementations.Net
|
||||||
{
|
{
|
||||||
public class NetSocket : ISocket
|
public class NetAcceptSocket : IAcceptSocket
|
||||||
{
|
{
|
||||||
public Socket Socket { get; private set; }
|
public Socket Socket { get; private set; }
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public bool DualMode { get; private set; }
|
public bool DualMode { get; private set; }
|
||||||
|
|
||||||
public NetSocket(Socket socket, ILogger logger, bool isDualMode)
|
public NetAcceptSocket(Socket socket, ILogger logger, bool isDualMode)
|
||||||
{
|
{
|
||||||
if (socket == null)
|
if (socket == null)
|
||||||
{
|
{
|
||||||
|
@ -47,6 +47,13 @@ namespace Emby.Common.Implementations.Net
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Connect(IpEndPointInfo endPoint)
|
||||||
|
{
|
||||||
|
var nativeEndpoint = NetworkManager.ToIPEndPoint(endPoint);
|
||||||
|
|
||||||
|
Socket.Connect(nativeEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
public void Close()
|
public void Close()
|
||||||
{
|
{
|
||||||
#if NET46
|
#if NET46
|
||||||
|
@ -82,7 +89,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
}
|
}
|
||||||
|
|
||||||
private SocketAcceptor _acceptor;
|
private SocketAcceptor _acceptor;
|
||||||
public void StartAccept(Action<ISocket> onAccept, Func<bool> isClosed)
|
public void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed)
|
||||||
{
|
{
|
||||||
_acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed, DualMode);
|
_acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed, DualMode);
|
||||||
|
|
|
@ -10,10 +10,10 @@ namespace Emby.Common.Implementations.Net
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly Socket _originalSocket;
|
private readonly Socket _originalSocket;
|
||||||
private readonly Func<bool> _isClosed;
|
private readonly Func<bool> _isClosed;
|
||||||
private readonly Action<ISocket> _onAccept;
|
private readonly Action<IAcceptSocket> _onAccept;
|
||||||
private readonly bool _isDualMode;
|
private readonly bool _isDualMode;
|
||||||
|
|
||||||
public SocketAcceptor(ILogger logger, Socket originalSocket, Action<ISocket> onAccept, Func<bool> isClosed, bool isDualMode)
|
public SocketAcceptor(ILogger logger, Socket originalSocket, Action<IAcceptSocket> onAccept, Func<bool> isClosed, bool isDualMode)
|
||||||
{
|
{
|
||||||
if (logger == null)
|
if (logger == null)
|
||||||
{
|
{
|
||||||
|
@ -54,7 +54,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// socket must be cleared since the context object is being reused
|
// acceptSocket must be cleared since the context object is being reused
|
||||||
acceptEventArg.AcceptSocket = null;
|
acceptEventArg.AcceptSocket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.acceptasync%28v=vs.110%29.aspx
|
// http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx
|
||||||
// Under certain conditions ConnectionReset can occur
|
// Under certain conditions ConnectionReset can occur
|
||||||
// Need to attept to re-accept
|
// Need to attept to re-accept
|
||||||
if (e.SocketError == SocketError.ConnectionReset)
|
if (e.SocketError == SocketError.ConnectionReset)
|
||||||
|
@ -117,7 +117,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
if (acceptSocket != null)
|
if (acceptSocket != null)
|
||||||
{
|
{
|
||||||
//ProcessAccept(acceptSocket);
|
//ProcessAccept(acceptSocket);
|
||||||
_onAccept(new NetSocket(acceptSocket, _logger, _isDualMode));
|
_onAccept(new NetAcceptSocket(acceptSocket, _logger, _isDualMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept the next connection request
|
// Accept the next connection request
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode)
|
public IAcceptSocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -46,7 +46,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
socket.DualMode = true;
|
socket.DualMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NetSocket(socket, _logger, dualMode);
|
return new NetAcceptSocket(socket, _logger, dualMode);
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (SocketException ex)
|
||||||
{
|
{
|
||||||
|
@ -54,13 +54,30 @@ namespace Emby.Common.Implementations.Net
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region ISocketFactory Members
|
public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
return new UdpSocket(retVal, new IpEndPointInfo(remoteAddress, remotePort));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (retVal != null)
|
||||||
|
retVal.Dispose();
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new UDP socket and binds it to the specified local port.
|
/// Creates a new UDP acceptSocket and binds it to the specified local port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="localPort">An integer specifying the local port to bind the socket to.</param>
|
/// <param name="localPort">An integer specifying the local port to bind the acceptSocket to.</param>
|
||||||
public IUdpSocket CreateUdpSocket(int localPort)
|
public ISocket CreateUdpSocket(int localPort)
|
||||||
{
|
{
|
||||||
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
|
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
|
||||||
|
|
||||||
|
@ -80,10 +97,10 @@ namespace Emby.Common.Implementations.Net
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
|
/// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An implementation of the <see cref="IUdpSocket"/> interface used by RSSDP components to perform socket operations.</returns>
|
/// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
|
||||||
public IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
|
public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
|
||||||
{
|
{
|
||||||
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
|
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
|
||||||
|
|
||||||
|
@ -108,13 +125,13 @@ namespace Emby.Common.Implementations.Net
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port.
|
/// Creates a new UDP acceptSocket that is a member of the specified multicast IP address, and binds it to the specified local port.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ipAddress">The multicast IP address to make the socket a member of.</param>
|
/// <param name="ipAddress">The multicast IP address to make the acceptSocket a member of.</param>
|
||||||
/// <param name="multicastTimeToLive">The multicast time to live value for the socket.</param>
|
/// <param name="multicastTimeToLive">The multicast time to live value for the acceptSocket.</param>
|
||||||
/// <param name="localPort">The number of the local port to bind to.</param>
|
/// <param name="localPort">The number of the local port to bind to.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
|
public ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
|
||||||
{
|
{
|
||||||
if (ipAddress == null) throw new ArgumentNullException("ipAddress");
|
if (ipAddress == null) throw new ArgumentNullException("ipAddress");
|
||||||
if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress");
|
if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress");
|
||||||
|
@ -128,7 +145,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
#if NET46
|
#if NET46
|
||||||
retVal.ExclusiveAddressUse = false;
|
retVal.ExclusiveAddressUse = false;
|
||||||
#else
|
#else
|
||||||
// The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket
|
// The ExclusiveAddressUse acceptSocket option is a Windows-specific option that, when set to "true," tells Windows not to allow another acceptSocket to use the same local address as this acceptSocket
|
||||||
// See https://github.com/dotnet/corefx/pull/11509 for more details
|
// See https://github.com/dotnet/corefx/pull/11509 for more details
|
||||||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
|
@ -154,7 +171,5 @@ namespace Emby.Common.Implementations.Net
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Emby.Common.Implementations.Net
|
||||||
// THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS
|
// THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS
|
||||||
// Be careful to check any changes compile and work for all platform projects it is shared in.
|
// Be careful to check any changes compile and work for all platform projects it is shared in.
|
||||||
|
|
||||||
internal sealed class UdpSocket : DisposableManagedObjectBase, IUdpSocket
|
internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket
|
||||||
{
|
{
|
||||||
|
|
||||||
#region Fields
|
#region Fields
|
||||||
|
@ -23,8 +23,6 @@ namespace Emby.Common.Implementations.Net
|
||||||
private int _LocalPort;
|
private int _LocalPort;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
|
||||||
|
|
||||||
public UdpSocket(Socket socket, int localPort, IPAddress ip)
|
public UdpSocket(Socket socket, int localPort, IPAddress ip)
|
||||||
{
|
{
|
||||||
if (socket == null) throw new ArgumentNullException("socket");
|
if (socket == null) throw new ArgumentNullException("socket");
|
||||||
|
@ -36,7 +34,13 @@ namespace Emby.Common.Implementations.Net
|
||||||
_Socket.Bind(new IPEndPoint(ip, _LocalPort));
|
_Socket.Bind(new IPEndPoint(ip, _LocalPort));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
public UdpSocket(Socket socket, IpEndPointInfo endPoint)
|
||||||
|
{
|
||||||
|
if (socket == null) throw new ArgumentNullException("socket");
|
||||||
|
|
||||||
|
_Socket = socket;
|
||||||
|
_Socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
|
||||||
|
}
|
||||||
|
|
||||||
public IpAddressInfo LocalIPAddress
|
public IpAddressInfo LocalIPAddress
|
||||||
{
|
{
|
||||||
|
@ -44,9 +48,9 @@ namespace Emby.Common.Implementations.Net
|
||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IUdpSocket Members
|
#region ISocket Members
|
||||||
|
|
||||||
public Task<SocketReceiveResult> ReceiveAsync()
|
public Task<SocketReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
|
|
@ -234,7 +234,7 @@ namespace Emby.Common.Implementations.Networking
|
||||||
// Try to exclude virtual adapters
|
// Try to exclude virtual adapters
|
||||||
// http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
|
// http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
|
||||||
var addr = ipProperties.GatewayAddresses.FirstOrDefault();
|
var addr = ipProperties.GatewayAddresses.FirstOrDefault();
|
||||||
if (addr == null|| string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
|
if (addr == null || string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new List<IPAddress>();
|
return new List<IPAddress>();
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ namespace Emby.Common.Implementations.Networking
|
||||||
/// Gets a random port number that is currently available
|
/// Gets a random port number that is currently available
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>System.Int32.</returns>
|
/// <returns>System.Int32.</returns>
|
||||||
public int GetRandomUnusedPort()
|
public int GetRandomUnusedTcpPort()
|
||||||
{
|
{
|
||||||
var listener = new TcpListener(IPAddress.Any, 0);
|
var listener = new TcpListener(IPAddress.Any, 0);
|
||||||
listener.Start();
|
listener.Start();
|
||||||
|
@ -284,6 +284,16 @@ namespace Emby.Common.Implementations.Networking
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetRandomUnusedUdpPort()
|
||||||
|
{
|
||||||
|
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
using (var udpClient = new UdpClient(localEndPoint))
|
||||||
|
{
|
||||||
|
var port = ((IPEndPoint)(udpClient.Client.LocalEndPoint)).Port;
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns MAC Address from first Network Card in Computer
|
/// Returns MAC Address from first Network Card in Computer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -83,6 +83,12 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Format = "srt",
|
Format = "srt",
|
||||||
Method = SubtitleDeliveryMethod.Embed
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
},
|
||||||
|
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.External,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,15 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,6 +214,14 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,15 @@ namespace Emby.Dlna.Profiles
|
||||||
MimeType = "video/mp4"
|
MimeType = "video/mp4"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,6 +210,15 @@ namespace Emby.Dlna.Profiles
|
||||||
MimeType = "video/mp4"
|
MimeType = "video/mp4"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,6 +269,15 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = DlnaProfileType.Audio
|
Type = DlnaProfileType.Audio
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -351,6 +351,15 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,6 +374,15 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,6 +292,15 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,6 +310,15 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,6 +310,15 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -255,6 +255,15 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = DlnaProfileType.Audio
|
Type = DlnaProfileType.Audio
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,6 +264,15 @@ namespace Emby.Dlna.Profiles
|
||||||
MimeType = "video/mp4"
|
MimeType = "video/mp4"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ namespace Emby.Dlna.Profiles
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
{
|
{
|
||||||
Container = "flac,ac3",
|
Container = "flac",
|
||||||
Type = DlnaProfileType.Audio
|
Type = DlnaProfileType.Audio
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -312,6 +312,15 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,6 +357,15 @@ namespace Emby.Dlna.Profiles
|
||||||
MimeType = "video/mp4"
|
MimeType = "video/mp4"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.Embed
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,5 +46,6 @@
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SubtitleProfiles>
|
<SubtitleProfiles>
|
||||||
<SubtitleProfile format="srt" method="Embed" />
|
<SubtitleProfile format="srt" method="Embed" />
|
||||||
|
<SubtitleProfile format="srt" method="External" />
|
||||||
</SubtitleProfiles>
|
</SubtitleProfiles>
|
||||||
</Profile>
|
</Profile>
|
|
@ -46,5 +46,6 @@
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SubtitleProfiles>
|
<SubtitleProfiles>
|
||||||
<SubtitleProfile format="srt" method="Embed" />
|
<SubtitleProfile format="srt" method="Embed" />
|
||||||
|
<SubtitleProfile format="srt" method="External" />
|
||||||
</SubtitleProfiles>
|
</SubtitleProfiles>
|
||||||
</Profile>
|
</Profile>
|
|
@ -52,5 +52,6 @@
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SubtitleProfiles>
|
<SubtitleProfiles>
|
||||||
<SubtitleProfile format="srt" method="Embed" />
|
<SubtitleProfile format="srt" method="Embed" />
|
||||||
|
<SubtitleProfile format="srt" method="External" />
|
||||||
</SubtitleProfiles>
|
</SubtitleProfiles>
|
||||||
</Profile>
|
</Profile>
|
|
@ -45,7 +45,7 @@
|
||||||
<DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
|
<DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
|
||||||
<DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />
|
<DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />
|
||||||
<DirectPlayProfile container="mp4" audioCodec="mp4" type="Audio" />
|
<DirectPlayProfile container="mp4" audioCodec="mp4" type="Audio" />
|
||||||
<DirectPlayProfile container="flac,ac3" type="Audio" />
|
<DirectPlayProfile container="flac" type="Audio" />
|
||||||
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" />
|
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" />
|
||||||
<DirectPlayProfile container="ogg" audioCodec="vorbis" type="Audio" />
|
<DirectPlayProfile container="ogg" audioCodec="vorbis" type="Audio" />
|
||||||
<DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" />
|
<DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" />
|
||||||
|
|
|
@ -52,5 +52,6 @@
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SubtitleProfiles>
|
<SubtitleProfiles>
|
||||||
<SubtitleProfile format="srt" method="Embed" />
|
<SubtitleProfile format="srt" method="Embed" />
|
||||||
|
<SubtitleProfile format="srt" method="External" />
|
||||||
</SubtitleProfiles>
|
</SubtitleProfiles>
|
||||||
</Profile>
|
</Profile>
|
|
@ -44,7 +44,9 @@ namespace Emby.Drawing.ImageMagick
|
||||||
"cr2",
|
"cr2",
|
||||||
"crw",
|
"crw",
|
||||||
"dng",
|
"dng",
|
||||||
"nef",
|
|
||||||
|
// Remove until supported
|
||||||
|
//"nef",
|
||||||
"orf",
|
"orf",
|
||||||
"pef",
|
"pef",
|
||||||
"arw",
|
"arw",
|
||||||
|
|
|
@ -76,9 +76,9 @@ namespace Emby.Server.Core
|
||||||
|
|
||||||
public class StreamFactory : IStreamFactory
|
public class StreamFactory : IStreamFactory
|
||||||
{
|
{
|
||||||
public Stream CreateNetworkStream(ISocket socket, bool ownsSocket)
|
public Stream CreateNetworkStream(IAcceptSocket acceptSocket, bool ownsSocket)
|
||||||
{
|
{
|
||||||
var netSocket = (NetSocket)socket;
|
var netSocket = (NetAcceptSocket)acceptSocket;
|
||||||
|
|
||||||
return new NetworkStream(netSocket.Socket, ownsSocket);
|
return new NetworkStream(netSocket.Socket, ownsSocket);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,15 @@ namespace Emby.Server.Core.Localization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return text.Normalize(form);
|
try
|
||||||
|
{
|
||||||
|
return text.Normalize(form);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
// if it still fails, return the original text
|
||||||
|
return text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string StripInvalidUnicodeCharacters(string str)
|
private static string StripInvalidUnicodeCharacters(string str)
|
||||||
|
|
|
@ -3388,10 +3388,10 @@ namespace Emby.Server.Implementations.Data
|
||||||
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
|
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
|
||||||
if (includeTypes.Length == 1)
|
if (includeTypes.Length == 1)
|
||||||
{
|
{
|
||||||
whereClauses.Add("type=@type" + paramSuffix);
|
whereClauses.Add("type=@type");
|
||||||
if (statement != null)
|
if (statement != null)
|
||||||
{
|
{
|
||||||
statement.TryBind("@type" + paramSuffix, includeTypes[0]);
|
statement.TryBind("@type", includeTypes[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (includeTypes.Length > 1)
|
else if (includeTypes.Length > 1)
|
||||||
|
@ -4936,7 +4936,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
ParentId = query.ParentId,
|
ParentId = query.ParentId,
|
||||||
IsPlayed = query.IsPlayed
|
IsPlayed = query.IsPlayed
|
||||||
};
|
};
|
||||||
var whereClauses = GetWhereClauses(typeSubQuery, null, "itemTypes");
|
var whereClauses = GetWhereClauses(typeSubQuery, null);
|
||||||
|
|
||||||
whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
|
whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
|
||||||
|
|
||||||
|
@ -5072,7 +5072,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
if (typeSubQuery != null)
|
if (typeSubQuery != null)
|
||||||
{
|
{
|
||||||
GetWhereClauses(typeSubQuery, null, "itemTypes");
|
GetWhereClauses(typeSubQuery, null);
|
||||||
}
|
}
|
||||||
BindSimilarParams(query, statement);
|
BindSimilarParams(query, statement);
|
||||||
GetWhereClauses(innerQuery, statement);
|
GetWhereClauses(innerQuery, statement);
|
||||||
|
@ -5110,7 +5110,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
if (typeSubQuery != null)
|
if (typeSubQuery != null)
|
||||||
{
|
{
|
||||||
GetWhereClauses(typeSubQuery, null, "itemTypes");
|
GetWhereClauses(typeSubQuery, null);
|
||||||
}
|
}
|
||||||
BindSimilarParams(query, statement);
|
BindSimilarParams(query, statement);
|
||||||
GetWhereClauses(innerQuery, statement);
|
GetWhereClauses(innerQuery, statement);
|
||||||
|
|
|
@ -195,13 +195,12 @@ namespace Emby.Server.Implementations.Devices
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = _config.GetUploadOptions();
|
var config = _config.GetUploadOptions();
|
||||||
if (!string.IsNullOrWhiteSpace(config.CameraUploadPath))
|
var path = config.CameraUploadPath;
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
{
|
{
|
||||||
return config.CameraUploadPath;
|
path = DefaultCameraUploadsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = DefaultCameraUploadsPath;
|
|
||||||
|
|
||||||
if (config.EnableCameraUploadSubfolders)
|
if (config.EnableCameraUploadSubfolders)
|
||||||
{
|
{
|
||||||
path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name));
|
path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name));
|
||||||
|
|
|
@ -491,7 +491,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (!(item is LiveTvProgram))
|
//if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess))
|
||||||
{
|
{
|
||||||
dto.PlayAccess = item.GetPlayAccess(user);
|
dto.PlayAccess = item.GetPlayAccess(user);
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,6 @@
|
||||||
<Compile Include="HttpServer\StreamWriter.cs" />
|
<Compile Include="HttpServer\StreamWriter.cs" />
|
||||||
<Compile Include="HttpServer\SwaggerService.cs" />
|
<Compile Include="HttpServer\SwaggerService.cs" />
|
||||||
<Compile Include="Images\BaseDynamicImageProvider.cs" />
|
<Compile Include="Images\BaseDynamicImageProvider.cs" />
|
||||||
<Compile Include="Intros\DefaultIntroProvider.cs" />
|
|
||||||
<Compile Include="IO\FileRefresher.cs" />
|
<Compile Include="IO\FileRefresher.cs" />
|
||||||
<Compile Include="IO\MbLinkShortcutHandler.cs" />
|
<Compile Include="IO\MbLinkShortcutHandler.cs" />
|
||||||
<Compile Include="IO\ThrottledStream.cs" />
|
<Compile Include="IO\ThrottledStream.cs" />
|
||||||
|
@ -170,9 +169,11 @@
|
||||||
<Compile Include="LiveTv\RecordingImageProvider.cs" />
|
<Compile Include="LiveTv\RecordingImageProvider.cs" />
|
||||||
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
|
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
|
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
|
||||||
|
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
|
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
|
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunLiveStream.cs" />
|
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" />
|
||||||
|
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
|
<Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
|
<Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
|
<Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
|
||||||
|
@ -238,7 +239,6 @@
|
||||||
<Compile Include="Sorting\AlbumComparer.cs" />
|
<Compile Include="Sorting\AlbumComparer.cs" />
|
||||||
<Compile Include="Sorting\AlphanumComparator.cs" />
|
<Compile Include="Sorting\AlphanumComparator.cs" />
|
||||||
<Compile Include="Sorting\ArtistComparer.cs" />
|
<Compile Include="Sorting\ArtistComparer.cs" />
|
||||||
<Compile Include="Sorting\BudgetComparer.cs" />
|
|
||||||
<Compile Include="Sorting\CommunityRatingComparer.cs" />
|
<Compile Include="Sorting\CommunityRatingComparer.cs" />
|
||||||
<Compile Include="Sorting\CriticRatingComparer.cs" />
|
<Compile Include="Sorting\CriticRatingComparer.cs" />
|
||||||
<Compile Include="Sorting\DateCreatedComparer.cs" />
|
<Compile Include="Sorting\DateCreatedComparer.cs" />
|
||||||
|
@ -257,7 +257,6 @@
|
||||||
<Compile Include="Sorting\PremiereDateComparer.cs" />
|
<Compile Include="Sorting\PremiereDateComparer.cs" />
|
||||||
<Compile Include="Sorting\ProductionYearComparer.cs" />
|
<Compile Include="Sorting\ProductionYearComparer.cs" />
|
||||||
<Compile Include="Sorting\RandomComparer.cs" />
|
<Compile Include="Sorting\RandomComparer.cs" />
|
||||||
<Compile Include="Sorting\RevenueComparer.cs" />
|
|
||||||
<Compile Include="Sorting\RuntimeComparer.cs" />
|
<Compile Include="Sorting\RuntimeComparer.cs" />
|
||||||
<Compile Include="Sorting\SeriesSortNameComparer.cs" />
|
<Compile Include="Sorting\SeriesSortNameComparer.cs" />
|
||||||
<Compile Include="Sorting\SortNameComparer.cs" />
|
<Compile Include="Sorting\SortNameComparer.cs" />
|
||||||
|
|
|
@ -579,7 +579,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ErrorHandler(new FileNotFoundException(), httpReq);
|
ErrorHandler(new FileNotFoundException(), httpReq, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException ex)
|
catch (OperationCanceledException ex)
|
||||||
|
@ -633,7 +633,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void Write(IResponse response, string text)
|
private void Write(IResponse response, string text)
|
||||||
{
|
{
|
||||||
var bOutput = Encoding.UTF8.GetBytes(text);
|
var bOutput = Encoding.UTF8.GetBytes(text);
|
||||||
|
|
|
@ -1,394 +0,0 @@
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Common.Security;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Model.Configuration;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Intros
|
|
||||||
{
|
|
||||||
public class DefaultIntroProvider : IIntroProvider
|
|
||||||
{
|
|
||||||
private readonly ISecurityManager _security;
|
|
||||||
private readonly ILocalizationManager _localization;
|
|
||||||
private readonly IConfigurationManager _serverConfig;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
|
||||||
|
|
||||||
public DefaultIntroProvider(ISecurityManager security, ILocalizationManager localization, IConfigurationManager serverConfig, ILibraryManager libraryManager, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager)
|
|
||||||
{
|
|
||||||
_security = security;
|
|
||||||
_localization = localization;
|
|
||||||
_serverConfig = serverConfig;
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_mediaSourceManager = mediaSourceManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, User user)
|
|
||||||
{
|
|
||||||
var config = GetOptions();
|
|
||||||
|
|
||||||
if (item is Movie)
|
|
||||||
{
|
|
||||||
if (!config.EnableIntrosForMovies)
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (item is Episode)
|
|
||||||
{
|
|
||||||
if (!config.EnableIntrosForEpisodes)
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var ratingLevel = string.IsNullOrWhiteSpace(item.OfficialRating)
|
|
||||||
? null
|
|
||||||
: _localization.GetRatingLevel(item.OfficialRating);
|
|
||||||
|
|
||||||
var candidates = new List<ItemWithTrailer>();
|
|
||||||
|
|
||||||
var trailerTypes = new List<TrailerType>();
|
|
||||||
var sourceTypes = new List<SourceType>();
|
|
||||||
|
|
||||||
if (config.EnableIntrosFromMoviesInLibrary)
|
|
||||||
{
|
|
||||||
trailerTypes.Add(TrailerType.LocalTrailer);
|
|
||||||
sourceTypes.Add(SourceType.Library);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsSupporter)
|
|
||||||
{
|
|
||||||
if (config.EnableIntrosFromUpcomingTrailers)
|
|
||||||
{
|
|
||||||
trailerTypes.Add(TrailerType.ComingSoonToTheaters);
|
|
||||||
sourceTypes.Clear();
|
|
||||||
}
|
|
||||||
if (config.EnableIntrosFromUpcomingDvdMovies)
|
|
||||||
{
|
|
||||||
trailerTypes.Add(TrailerType.ComingSoonToDvd);
|
|
||||||
sourceTypes.Clear();
|
|
||||||
}
|
|
||||||
if (config.EnableIntrosFromUpcomingStreamingMovies)
|
|
||||||
{
|
|
||||||
trailerTypes.Add(TrailerType.ComingSoonToStreaming);
|
|
||||||
sourceTypes.Clear();
|
|
||||||
}
|
|
||||||
if (config.EnableIntrosFromSimilarMovies)
|
|
||||||
{
|
|
||||||
trailerTypes.Add(TrailerType.Archive);
|
|
||||||
sourceTypes.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trailerTypes.Count > 0)
|
|
||||||
{
|
|
||||||
if (trailerTypes.Count >= 5)
|
|
||||||
{
|
|
||||||
trailerTypes.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// hack - can't filter by user library because local trailers get TopParentId =null in the db.
|
|
||||||
// for now we have to use a post-query filter afterwards to solve that
|
|
||||||
var trailerResult = _libraryManager.GetItemList(new InternalItemsQuery
|
|
||||||
{
|
|
||||||
IncludeItemTypes = new[] { typeof(Trailer).Name },
|
|
||||||
TrailerTypes = trailerTypes.ToArray(),
|
|
||||||
SimilarTo = item,
|
|
||||||
//IsPlayed = config.EnableIntrosForWatchedContent ? (bool?)null : false,
|
|
||||||
MaxParentalRating = config.EnableIntrosParentalControl ? ratingLevel : null,
|
|
||||||
BlockUnratedItems = config.EnableIntrosParentalControl ? new[] { UnratedItem.Trailer } : new UnratedItem[] { },
|
|
||||||
|
|
||||||
// Account for duplicates by imdb id, since the database doesn't support this yet
|
|
||||||
Limit = config.TrailerLimit * 4,
|
|
||||||
SourceTypes = sourceTypes.ToArray()
|
|
||||||
})
|
|
||||||
.Where(i => string.IsNullOrWhiteSpace(i.GetProviderId(MetadataProviders.Imdb)) || !string.Equals(i.GetProviderId(MetadataProviders.Imdb), item.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase))
|
|
||||||
.Where(i => i.IsVisibleStandalone(user))
|
|
||||||
.Where(i => config.EnableIntrosForWatchedContent || !i.IsPlayed(user))
|
|
||||||
.Take(config.TrailerLimit);
|
|
||||||
|
|
||||||
candidates.AddRange(trailerResult.Select(i => new ItemWithTrailer
|
|
||||||
{
|
|
||||||
Item = i,
|
|
||||||
Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer,
|
|
||||||
LibraryManager = _libraryManager
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetResult(item, candidates, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IntroInfo> GetResult(BaseItem item, IEnumerable<ItemWithTrailer> candidates, CinemaModeConfiguration config)
|
|
||||||
{
|
|
||||||
var customIntros = !string.IsNullOrWhiteSpace(config.CustomIntroPath) ?
|
|
||||||
GetCustomIntros(config) :
|
|
||||||
new List<IntroInfo>();
|
|
||||||
|
|
||||||
var mediaInfoIntros = !string.IsNullOrWhiteSpace(config.MediaInfoIntroPath) ?
|
|
||||||
GetMediaInfoIntros(config, item) :
|
|
||||||
new List<IntroInfo>();
|
|
||||||
|
|
||||||
// Avoid implicitly captured closure
|
|
||||||
return candidates.Select(i => i.IntroInfo)
|
|
||||||
.Concat(customIntros.Take(1))
|
|
||||||
.Concat(mediaInfoIntros);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CinemaModeConfiguration GetOptions()
|
|
||||||
{
|
|
||||||
return _serverConfig.GetConfiguration<CinemaModeConfiguration>("cinemamode");
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<IntroInfo> GetCustomIntros(CinemaModeConfiguration options)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return GetCustomIntroFiles(options, true, false)
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.Select(i => new IntroInfo
|
|
||||||
{
|
|
||||||
Path = i
|
|
||||||
|
|
||||||
}).ToList();
|
|
||||||
}
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IntroInfo> GetMediaInfoIntros(CinemaModeConfiguration options, BaseItem item)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var hasMediaSources = item as IHasMediaSources;
|
|
||||||
|
|
||||||
if (hasMediaSources == null)
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var mediaSource = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (mediaSource == null)
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
|
||||||
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
|
||||||
|
|
||||||
var allIntros = GetCustomIntroFiles(options, false, true)
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.Select(i => new IntroInfo
|
|
||||||
{
|
|
||||||
Path = i
|
|
||||||
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var returnResult = new List<IntroInfo>();
|
|
||||||
|
|
||||||
if (videoStream != null)
|
|
||||||
{
|
|
||||||
returnResult.AddRange(GetMediaInfoIntrosByVideoStream(allIntros, videoStream).Take(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioStream != null)
|
|
||||||
{
|
|
||||||
returnResult.AddRange(GetMediaInfoIntrosByAudioStream(allIntros, audioStream).Take(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1));
|
|
||||||
|
|
||||||
return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IntroInfo> GetMediaInfoIntrosByVideoStream(List<IntroInfo> allIntros, MediaStream stream)
|
|
||||||
{
|
|
||||||
var codec = stream.Codec;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(codec))
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return allIntros
|
|
||||||
.Where(i => IsMatch(i.Path, codec))
|
|
||||||
.OrderBy(i => Guid.NewGuid());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IntroInfo> GetMediaInfoIntrosByAudioStream(List<IntroInfo> allIntros, MediaStream stream)
|
|
||||||
{
|
|
||||||
var codec = stream.Codec;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(codec))
|
|
||||||
{
|
|
||||||
return new List<IntroInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return allIntros
|
|
||||||
.Where(i => IsAudioMatch(i.Path, stream))
|
|
||||||
.OrderBy(i => Guid.NewGuid());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<IntroInfo> GetMediaInfoIntrosByTags(List<IntroInfo> allIntros, List<string> tags)
|
|
||||||
{
|
|
||||||
return allIntros
|
|
||||||
.Where(i => tags.Any(t => IsMatch(i.Path, t)))
|
|
||||||
.OrderBy(i => Guid.NewGuid());
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsMatch(string file, string attribute)
|
|
||||||
{
|
|
||||||
var filename = Path.GetFileNameWithoutExtension(file) ?? string.Empty;
|
|
||||||
filename = Normalize(filename);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(filename))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
attribute = Normalize(attribute);
|
|
||||||
if (string.IsNullOrWhiteSpace(attribute))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(filename, attribute, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Normalize(string value)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsAudioMatch(string path, MediaStream stream)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(stream.Codec))
|
|
||||||
{
|
|
||||||
if (IsMatch(path, stream.Codec))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrWhiteSpace(stream.Profile))
|
|
||||||
{
|
|
||||||
if (IsMatch(path, stream.Profile))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<string> GetCustomIntroFiles(CinemaModeConfiguration options, bool enableCustomIntros, bool enableMediaInfoIntros)
|
|
||||||
{
|
|
||||||
var list = new List<string>();
|
|
||||||
|
|
||||||
if (enableCustomIntros && !string.IsNullOrWhiteSpace(options.CustomIntroPath))
|
|
||||||
{
|
|
||||||
list.AddRange(_fileSystem.GetFilePaths(options.CustomIntroPath, true)
|
|
||||||
.Where(_libraryManager.IsVideoFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableMediaInfoIntros && !string.IsNullOrWhiteSpace(options.MediaInfoIntroPath))
|
|
||||||
{
|
|
||||||
list.AddRange(_fileSystem.GetFilePaths(options.MediaInfoIntroPath, true)
|
|
||||||
.Where(_libraryManager.IsVideoFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.Distinct(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<string> GetAllIntroFiles()
|
|
||||||
{
|
|
||||||
return GetCustomIntroFiles(GetOptions(), true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsSupporter
|
|
||||||
{
|
|
||||||
get { return _security.IsMBSupporter; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get { return "Default"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class ItemWithTrailer
|
|
||||||
{
|
|
||||||
internal BaseItem Item;
|
|
||||||
internal ItemWithTrailerType Type;
|
|
||||||
internal ILibraryManager LibraryManager;
|
|
||||||
|
|
||||||
public IntroInfo IntroInfo
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var id = Item.Id;
|
|
||||||
|
|
||||||
if (Type == ItemWithTrailerType.ItemWithTrailer)
|
|
||||||
{
|
|
||||||
var hasTrailers = Item as IHasTrailers;
|
|
||||||
|
|
||||||
if (hasTrailers != null)
|
|
||||||
{
|
|
||||||
id = hasTrailers.LocalTrailerIds.FirstOrDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new IntroInfo
|
|
||||||
{
|
|
||||||
ItemId = id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum ItemWithTrailerType
|
|
||||||
{
|
|
||||||
ChannelTrailer,
|
|
||||||
ItemWithTrailer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CinemaModeConfigurationFactory : IConfigurationFactory
|
|
||||||
{
|
|
||||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
|
||||||
{
|
|
||||||
return new[]
|
|
||||||
{
|
|
||||||
new ConfigurationStore
|
|
||||||
{
|
|
||||||
ConfigurationType = typeof(CinemaModeConfiguration),
|
|
||||||
Key = "cinemamode"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -409,24 +409,46 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
if (options.DeleteFileLocation && locationType != LocationType.Remote && locationType != LocationType.Virtual)
|
if (options.DeleteFileLocation && locationType != LocationType.Remote && locationType != LocationType.Virtual)
|
||||||
{
|
{
|
||||||
|
// Assume only the first is required
|
||||||
|
// Add this flag to GetDeletePaths if required in the future
|
||||||
|
var isRequiredForDelete = true;
|
||||||
|
|
||||||
foreach (var fileSystemInfo in item.GetDeletePaths().ToList())
|
foreach (var fileSystemInfo in item.GetDeletePaths().ToList())
|
||||||
{
|
{
|
||||||
if (fileSystemInfo.IsDirectory)
|
try
|
||||||
{
|
{
|
||||||
_logger.Debug("Deleting path {0}", fileSystemInfo.FullName);
|
if (fileSystemInfo.IsDirectory)
|
||||||
_fileSystem.DeleteDirectory(fileSystemInfo.FullName, true);
|
{
|
||||||
|
_logger.Debug("Deleting path {0}", fileSystemInfo.FullName);
|
||||||
|
_fileSystem.DeleteDirectory(fileSystemInfo.FullName, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Debug("Deleting path {0}", fileSystemInfo.FullName);
|
||||||
|
_fileSystem.DeleteFile(fileSystemInfo.FullName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (IOException)
|
||||||
{
|
{
|
||||||
_logger.Debug("Deleting path {0}", fileSystemInfo.FullName);
|
if (isRequiredForDelete)
|
||||||
_fileSystem.DeleteFile(fileSystemInfo.FullName);
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
if (isRequiredForDelete)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isRequiredForDelete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
await parent.ValidateChildren(new Progress<double>(), CancellationToken.None)
|
await parent.ValidateChildren(new Progress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false) .ConfigureAwait(false);
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (parent != null)
|
else if (parent != null)
|
||||||
|
|
|
@ -434,6 +434,11 @@ namespace Emby.Server.Implementations.Library
|
||||||
Policy = user.Policy
|
Policy = user.Policy
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!hasPassword && Users.Count() == 1)
|
||||||
|
{
|
||||||
|
dto.EnableAutoLogin = true;
|
||||||
|
}
|
||||||
|
|
||||||
var image = user.GetImageInfo(ImageType.Primary, 0);
|
var image = user.GetImageInfo(ImageType.Primary, 0);
|
||||||
|
|
||||||
if (image != null)
|
if (image != null)
|
||||||
|
|
|
@ -282,6 +282,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
var showId = programInfo.programID ?? string.Empty;
|
var showId = programInfo.programID ?? string.Empty;
|
||||||
|
|
||||||
|
if (!info.IsSeries)
|
||||||
|
{
|
||||||
|
// It's also a series if it starts with SH
|
||||||
|
info.IsSeries = showId.StartsWith("SH", StringComparison.OrdinalIgnoreCase) && showId.Length >= 14;
|
||||||
|
}
|
||||||
|
|
||||||
// According to SchedulesDirect, these are generic, unidentified episodes
|
// According to SchedulesDirect, these are generic, unidentified episodes
|
||||||
// SH005316560000
|
// SH005316560000
|
||||||
var hasUniqueShowId = !showId.StartsWith("SH", StringComparison.OrdinalIgnoreCase) ||
|
var hasUniqueShowId = !showId.StartsWith("SH", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
@ -331,7 +337,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
{
|
{
|
||||||
var gracenote = details.metadata.Find(x => x.Gracenote != null).Gracenote;
|
var gracenote = details.metadata.Find(x => x.Gracenote != null).Gracenote;
|
||||||
info.SeasonNumber = gracenote.season;
|
info.SeasonNumber = gracenote.season;
|
||||||
info.EpisodeNumber = gracenote.episode;
|
|
||||||
|
if (gracenote.episode > 0)
|
||||||
|
{
|
||||||
|
info.EpisodeNumber = gracenote.episode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv
|
namespace Emby.Server.Implementations.LiveTv
|
||||||
|
@ -16,6 +15,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
const int AnalyzeDurationMs = 2000;
|
||||||
|
|
||||||
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
|
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
|
||||||
{
|
{
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
|
@ -34,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
Protocol = mediaSource.Protocol,
|
Protocol = mediaSource.Protocol,
|
||||||
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
||||||
ExtractChapters = false,
|
ExtractChapters = false,
|
||||||
AnalyzeDurationSections = 2
|
AnalyzeDurationMs = AnalyzeDurationMs
|
||||||
|
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -98,6 +99,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
|
|
||||||
// Try to estimate this
|
// Try to estimate this
|
||||||
mediaSource.InferTotalBitrate(true);
|
mediaSource.InferTotalBitrate(true);
|
||||||
|
|
||||||
|
mediaSource.AnalyzeDurationMs = AnalyzeDurationMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1596,6 +1596,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
IsFolder = false,
|
IsFolder = false,
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false,
|
||||||
Limit = query.Limit,
|
Limit = query.Limit,
|
||||||
|
StartIndex = query.StartIndex,
|
||||||
SortBy = new[] { ItemSortBy.DateCreated },
|
SortBy = new[] { ItemSortBy.DateCreated },
|
||||||
SortOrder = SortOrder.Descending,
|
SortOrder = SortOrder.Descending,
|
||||||
EnableTotalRecordCount = query.EnableTotalRecordCount,
|
EnableTotalRecordCount = query.EnableTotalRecordCount,
|
||||||
|
|
|
@ -28,13 +28,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
private readonly ISocketFactory _socketFactory;
|
||||||
|
private readonly INetworkManager _networkManager;
|
||||||
|
|
||||||
public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost)
|
public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager)
|
||||||
: base(config, logger, jsonSerializer, mediaEncoder)
|
: base(config, logger, jsonSerializer, mediaEncoder)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
_socketFactory = socketFactory;
|
||||||
|
_networkManager = networkManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name
|
public string Name
|
||||||
|
@ -65,9 +69,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
|
private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
var options = new HttpRequestOptions
|
||||||
{
|
{
|
||||||
Url = string.Format("{0}/lineup.json", GetApiUrl(info, false)),
|
Url = model.LineupURL,
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
BufferContent = false
|
BufferContent = false
|
||||||
};
|
};
|
||||||
|
@ -84,11 +90,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class HdHomerunChannelInfo : ChannelInfo
|
||||||
|
{
|
||||||
|
public bool IsLegacyTuner { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false);
|
var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return lineup.Select(i => new ChannelInfo
|
return lineup.Select(i => new HdHomerunChannelInfo
|
||||||
{
|
{
|
||||||
Name = i.GuideName,
|
Name = i.GuideName,
|
||||||
Number = i.GuideNumber,
|
Number = i.GuideNumber,
|
||||||
|
@ -98,20 +110,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
IsHD = i.HD == 1,
|
IsHD = i.HD == 1,
|
||||||
AudioCodec = i.AudioCodec,
|
AudioCodec = i.AudioCodec,
|
||||||
VideoCodec = i.VideoCodec,
|
VideoCodec = i.VideoCodec,
|
||||||
ChannelType = ChannelType.TV
|
ChannelType = ChannelType.TV,
|
||||||
|
IsLegacyTuner = (i.URL ?? string.Empty).StartsWith("hdhomerun", StringComparison.OrdinalIgnoreCase),
|
||||||
|
Url = i.URL
|
||||||
|
|
||||||
}).ToList();
|
}).Cast<ChannelInfo>().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
|
private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
|
||||||
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
|
private async Task<DiscoverResponse> GetModelInfo(TunerHostInfo info, bool throwAllExceptions, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
lock (_modelCache)
|
lock (_modelCache)
|
||||||
{
|
{
|
||||||
DiscoverResponse response;
|
DiscoverResponse response;
|
||||||
if (_modelCache.TryGetValue(info.Url, out response))
|
if (_modelCache.TryGetValue(info.Url, out response))
|
||||||
{
|
{
|
||||||
return response.ModelNumber;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,72 +144,70 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
||||||
|
|
||||||
lock (_modelCache)
|
if (!string.IsNullOrWhiteSpace(info.Id))
|
||||||
{
|
{
|
||||||
_modelCache[info.Id] = response;
|
lock (_modelCache)
|
||||||
|
{
|
||||||
|
_modelCache[info.Id] = response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.ModelNumber;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
|
if (!throwAllExceptions && ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
|
||||||
{
|
{
|
||||||
var defaultValue = "HDHR";
|
var defaultValue = "HDHR";
|
||||||
// HDHR4 doesn't have this api
|
var response = new DiscoverResponse
|
||||||
lock (_modelCache)
|
|
||||||
{
|
{
|
||||||
_modelCache[info.Id] = new DiscoverResponse
|
ModelNumber = defaultValue
|
||||||
|
};
|
||||||
|
if (!string.IsNullOrWhiteSpace(info.Id))
|
||||||
|
{
|
||||||
|
// HDHR4 doesn't have this api
|
||||||
|
lock (_modelCache)
|
||||||
{
|
{
|
||||||
ModelNumber = defaultValue
|
_modelCache[info.Id] = response;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
|
private async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
|
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
using (var stream = await _httpClient.Get(new HttpRequestOptions()
|
var tuners = new List<LiveTvTunerInfo>();
|
||||||
{
|
|
||||||
Url = string.Format("{0}/tuners.html", GetApiUrl(info, false)),
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
|
|
||||||
BufferContent = false
|
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
var uri = new Uri(GetApiUrl(info, false));
|
||||||
|
|
||||||
|
using (var manager = new HdHomerunManager(_socketFactory))
|
||||||
{
|
{
|
||||||
var tuners = new List<LiveTvTunerInfo>();
|
// Legacy HdHomeruns are IPv4 only
|
||||||
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
|
var ipInfo = _networkManager.ParseIpAddress(uri.Host);
|
||||||
|
|
||||||
|
for (int i = 0; i < model.TunerCount; ++i)
|
||||||
{
|
{
|
||||||
while (!sr.EndOfStream)
|
var name = String.Format("Tuner {0}", i + 1);
|
||||||
|
var currentChannel = "none"; /// @todo Get current channel and map back to Station Id
|
||||||
|
var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
|
||||||
|
LiveTvTunerStatus status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
|
||||||
|
tuners.Add(new LiveTvTunerInfo
|
||||||
{
|
{
|
||||||
string line = StripXML(sr.ReadLine());
|
Name = name,
|
||||||
if (line.Contains("Channel"))
|
SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
|
||||||
{
|
ProgramName = currentChannel,
|
||||||
LiveTvTunerStatus status;
|
Status = status
|
||||||
var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
|
});
|
||||||
var name = line.Substring(0, index - 1);
|
|
||||||
var currentChannel = line.Substring(index + 7);
|
|
||||||
if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
|
|
||||||
tuners.Add(new LiveTvTunerInfo
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
SourceType = string.IsNullOrWhiteSpace(model) ? Name : model,
|
|
||||||
ProgramName = currentChannel,
|
|
||||||
Status = status
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return tuners;
|
|
||||||
}
|
}
|
||||||
|
return tuners;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
||||||
|
@ -244,34 +256,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
return uri.AbsoluteUri.TrimEnd('/');
|
return uri.AbsoluteUri.TrimEnd('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string StripXML(string source)
|
|
||||||
{
|
|
||||||
char[] buffer = new char[source.Length];
|
|
||||||
int bufferIndex = 0;
|
|
||||||
bool inside = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < source.Length; i++)
|
|
||||||
{
|
|
||||||
char let = source[i];
|
|
||||||
if (let == '<')
|
|
||||||
{
|
|
||||||
inside = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (let == '>')
|
|
||||||
{
|
|
||||||
inside = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!inside)
|
|
||||||
{
|
|
||||||
buffer[bufferIndex] = let;
|
|
||||||
bufferIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new string(buffer, 0, bufferIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Channels
|
private class Channels
|
||||||
{
|
{
|
||||||
public string GuideNumber { get; set; }
|
public string GuideNumber { get; set; }
|
||||||
|
@ -284,13 +268,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
public int HD { get; set; }
|
public int HD { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MediaSourceInfo> GetMediaSource(TunerHostInfo info, string channelId, string profile)
|
private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, ChannelInfo channelInfo, string profile)
|
||||||
{
|
{
|
||||||
int? width = null;
|
int? width = null;
|
||||||
int? height = null;
|
int? height = null;
|
||||||
bool isInterlaced = true;
|
bool isInterlaced = true;
|
||||||
string videoCodec = null;
|
string videoCodec = null;
|
||||||
string audioCodec = "ac3";
|
string audioCodec = null;
|
||||||
|
|
||||||
int? videoBitrate = null;
|
int? videoBitrate = null;
|
||||||
int? audioBitrate = null;
|
int? audioBitrate = null;
|
||||||
|
@ -344,21 +328,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
videoBitrate = 1000000;
|
videoBitrate = 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
|
if (channelInfo != null)
|
||||||
var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase));
|
|
||||||
if (channel != null)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(videoCodec))
|
if (string.IsNullOrWhiteSpace(videoCodec))
|
||||||
{
|
{
|
||||||
videoCodec = channel.VideoCodec;
|
videoCodec = channelInfo.VideoCodec;
|
||||||
}
|
}
|
||||||
audioCodec = channel.AudioCodec;
|
audioCodec = channelInfo.AudioCodec;
|
||||||
|
|
||||||
if (!videoBitrate.HasValue)
|
if (!videoBitrate.HasValue)
|
||||||
{
|
{
|
||||||
videoBitrate = (channel.IsHD ?? true) ? 15000000 : 2000000;
|
videoBitrate = (channelInfo.IsHD ?? true) ? 15000000 : 2000000;
|
||||||
}
|
}
|
||||||
audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000;
|
audioBitrate = (channelInfo.IsHD ?? true) ? 448000 : 192000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize
|
// normalize
|
||||||
|
@ -443,6 +425,82 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
return channelId.Split('_')[1];
|
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,
|
||||||
|
Protocol = MediaProtocol.Udp,
|
||||||
|
MediaStreams = new List<MediaStream>
|
||||||
|
{
|
||||||
|
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 = true,
|
||||||
|
BufferMs = 0,
|
||||||
|
Container = "ts",
|
||||||
|
Id = id,
|
||||||
|
SupportsDirectPlay = false,
|
||||||
|
SupportsDirectStream = true,
|
||||||
|
SupportsTranscoding = true,
|
||||||
|
IsInfiniteStream = true
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaSource.InferTotalBitrate();
|
||||||
|
|
||||||
|
return mediaSource;
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
|
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var list = new List<MediaSourceInfo>();
|
var list = new List<MediaSourceInfo>();
|
||||||
|
@ -453,35 +511,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
}
|
}
|
||||||
var hdhrId = GetHdHrIdFromChannelId(channelId);
|
var hdhrId = GetHdHrIdFromChannelId(channelId);
|
||||||
|
|
||||||
try
|
var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
var hdHomerunChannelInfo = channelInfo as HdHomerunChannelInfo;
|
||||||
|
|
||||||
|
var isLegacyTuner = hdHomerunChannelInfo != null && hdHomerunChannelInfo.IsLegacyTuner;
|
||||||
|
|
||||||
|
if (isLegacyTuner)
|
||||||
{
|
{
|
||||||
var model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
|
list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo));
|
||||||
model = model ?? string.Empty;
|
}
|
||||||
|
else
|
||||||
if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
list.Add(await GetMediaSource(info, hdhrId, "native").ConfigureAwait(false));
|
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
var model = modelInfo == null ? string.Empty : (modelInfo.ModelNumber ?? string.Empty);
|
||||||
|
|
||||||
if (info.AllowHWTranscoding)
|
if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
|
||||||
{
|
{
|
||||||
list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
|
||||||
|
|
||||||
list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false));
|
if (info.AllowHWTranscoding)
|
||||||
list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false));
|
{
|
||||||
list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
|
||||||
list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false));
|
|
||||||
list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
|
||||||
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
|
||||||
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
|
||||||
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
|
||||||
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
catch
|
||||||
catch
|
{
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.Count == 0)
|
if (list.Count == 0)
|
||||||
{
|
{
|
||||||
list.Add(await GetMediaSource(info, hdhrId, "native").ConfigureAwait(false));
|
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
|
@ -509,11 +581,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
}
|
}
|
||||||
var hdhrId = GetHdHrIdFromChannelId(channelId);
|
var hdhrId = GetHdHrIdFromChannelId(channelId);
|
||||||
|
|
||||||
var mediaSource = await GetMediaSource(info, hdhrId, profile).ConfigureAwait(false);
|
var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var liveStream = new HdHomerunLiveStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
|
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
|
||||||
liveStream.EnableStreamSharing = true;
|
|
||||||
return liveStream;
|
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
|
||||||
|
{
|
||||||
|
var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
|
||||||
|
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
|
||||||
|
//var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
|
||||||
|
//return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Validate(TunerHostInfo info)
|
public async Task Validate(TunerHostInfo info)
|
||||||
|
@ -531,18 +618,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Test it by pulling down the lineup
|
// Test it by pulling down the lineup
|
||||||
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
var modelInfo = await GetModelInfo(info, true, CancellationToken.None).ConfigureAwait(false);
|
||||||
{
|
info.DeviceId = modelInfo.DeviceID;
|
||||||
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
|
||||||
CancellationToken = CancellationToken.None,
|
|
||||||
BufferContent = false
|
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
|
||||||
|
|
||||||
info.DeviceId = response.DeviceID;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
|
@ -573,6 +650,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
public string DeviceAuth { get; set; }
|
public string DeviceAuth { get; set; }
|
||||||
public string BaseURL { get; set; }
|
public string BaseURL { get; set; }
|
||||||
public string LineupURL { get; set; }
|
public string LineupURL { get; set; }
|
||||||
|
public int TunerCount { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
public class HdHomerunLiveStream : LiveStream, IDirectStreamProvider
|
public class HdHomerunHttpStream : LiveStream, IDirectStreamProvider
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
|
@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
|
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
private readonly MulticastStream _multicastStream;
|
private readonly MulticastStream _multicastStream;
|
||||||
|
|
||||||
public HdHomerunLiveStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
|
public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
|
||||||
: base(mediaSource)
|
: base(mediaSource)
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
|
@ -0,0 +1,453 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
|
||||||
|
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
{
|
||||||
|
public interface IHdHomerunChannelCommands
|
||||||
|
{
|
||||||
|
IEnumerable<Tuple<string, string>> GetCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
|
||||||
|
{
|
||||||
|
private string _channel;
|
||||||
|
private string _program;
|
||||||
|
public LegacyHdHomerunChannelCommands(string url)
|
||||||
|
{
|
||||||
|
// parse url for channel and program
|
||||||
|
var regExp = new Regex(@"\/ch(\d+)-?(\d*)");
|
||||||
|
var match = regExp.Match(url);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
_channel = match.Groups[1].Value;
|
||||||
|
_program = match.Groups[2].Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Tuple<string, string>> GetCommands()
|
||||||
|
{
|
||||||
|
var commands = new List<Tuple<string, string>>();
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(_channel))
|
||||||
|
commands.Add(Tuple.Create("channel", _channel));
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(_program))
|
||||||
|
commands.Add(Tuple.Create("program", _program));
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HdHomerunChannelCommands : IHdHomerunChannelCommands
|
||||||
|
{
|
||||||
|
private string _channel;
|
||||||
|
|
||||||
|
public HdHomerunChannelCommands(string channel)
|
||||||
|
{
|
||||||
|
_channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Tuple<string, string>> GetCommands()
|
||||||
|
{
|
||||||
|
var commands = new List<Tuple<string, string>>();
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(_channel))
|
||||||
|
commands.Add(Tuple.Create("vchannel", _channel));
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HdHomerunManager : IDisposable
|
||||||
|
{
|
||||||
|
public static int HdHomeRunPort = 65001;
|
||||||
|
// Message constants
|
||||||
|
private static byte GetSetName = 3;
|
||||||
|
private static byte GetSetValue = 4;
|
||||||
|
private static byte GetSetLockkey = 21;
|
||||||
|
private static ushort GetSetRequest = 4;
|
||||||
|
private static ushort GetSetReply = 5;
|
||||||
|
|
||||||
|
private uint? _lockkey = null;
|
||||||
|
private int _activeTuner = 0;
|
||||||
|
private readonly ISocketFactory _socketFactory;
|
||||||
|
private IpAddressInfo _remoteIp;
|
||||||
|
|
||||||
|
public HdHomerunManager(ISocketFactory socketFactory)
|
||||||
|
{
|
||||||
|
_socketFactory = socketFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
var task = StopStreaming();
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CheckTunerAvailability(IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using (var socket = _socketFactory.CreateTcpSocket(remoteIp, HdHomeRunPort))
|
||||||
|
{
|
||||||
|
return await CheckTunerAvailability(socket, remoteIp, tuner, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> CheckTunerAvailability(ISocket socket, IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ipEndPoint = new IpEndPointInfo(remoteIp, HdHomeRunPort);
|
||||||
|
|
||||||
|
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
|
||||||
|
await socket.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken);
|
||||||
|
var response = await socket.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
string returnVal;
|
||||||
|
ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal);
|
||||||
|
|
||||||
|
return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartStreaming(IpAddressInfo remoteIp, IpAddressInfo localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_remoteIp = remoteIp;
|
||||||
|
|
||||||
|
using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
|
||||||
|
{
|
||||||
|
if (!_lockkey.HasValue)
|
||||||
|
{
|
||||||
|
var rand = new Random();
|
||||||
|
_lockkey = (uint)rand.Next();
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
|
||||||
|
|
||||||
|
for (int i = 0; i < numTuners; ++i)
|
||||||
|
{
|
||||||
|
if (!await CheckTunerAvailability(tcpClient, _remoteIp, i, cancellationToken).ConfigureAwait(false))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_activeTuner = i;
|
||||||
|
var lockKeyString = String.Format("{0:d}", _lockkey.Value);
|
||||||
|
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
|
||||||
|
await tcpClient.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||||
|
var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
string returnVal;
|
||||||
|
// parse response to make sure it worked
|
||||||
|
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var commandList = commands.GetCommands();
|
||||||
|
foreach(Tuple<string,string> command in commandList)
|
||||||
|
{
|
||||||
|
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, _lockkey.Value);
|
||||||
|
await tcpClient.SendAsync(channelMsg, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||||
|
response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
// parse response to make sure it worked
|
||||||
|
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
|
||||||
|
{
|
||||||
|
await ReleaseLockkey(tcpClient).ConfigureAwait(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort);
|
||||||
|
var targetMsg = CreateSetMessage(i, "target", targetValue, _lockkey.Value);
|
||||||
|
|
||||||
|
await tcpClient.SendAsync(targetMsg, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||||
|
response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
// parse response to make sure it worked
|
||||||
|
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
|
||||||
|
{
|
||||||
|
await ReleaseLockkey(tcpClient).ConfigureAwait(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ChangeChannel(IHdHomerunChannelCommands commands, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!_lockkey.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
|
||||||
|
{
|
||||||
|
var commandList = commands.GetCommands();
|
||||||
|
foreach (Tuple<string, string> command in commandList)
|
||||||
|
{
|
||||||
|
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey.Value);
|
||||||
|
await tcpClient.SendAsync(channelMsg, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false);
|
||||||
|
var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
// parse response to make sure it worked
|
||||||
|
string returnVal;
|
||||||
|
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopStreaming()
|
||||||
|
{
|
||||||
|
if (!_lockkey.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (var socket = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
|
||||||
|
{
|
||||||
|
await ReleaseLockkey(socket).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReleaseLockkey(ISocket tcpClient)
|
||||||
|
{
|
||||||
|
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", _lockkey);
|
||||||
|
await tcpClient.SendAsync(releaseTarget, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
|
||||||
|
await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", _lockkey);
|
||||||
|
_lockkey = null;
|
||||||
|
await tcpClient.SendAsync(releaseKeyMsg, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
|
||||||
|
await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] CreateGetMessage(int tuner, string name)
|
||||||
|
{
|
||||||
|
var byteName = Encoding.UTF8.GetBytes(String.Format("/tuner{0}/{1}\0", tuner, name));
|
||||||
|
int messageLength = byteName.Length + 10; // 4 bytes for header + 4 bytes for crc + 2 bytes for tag name and length
|
||||||
|
|
||||||
|
var message = new byte[messageLength];
|
||||||
|
|
||||||
|
int offset = InsertHeaderAndName(byteName, messageLength, message);
|
||||||
|
|
||||||
|
bool flipEndian = BitConverter.IsLittleEndian;
|
||||||
|
|
||||||
|
// calculate crc and insert at the end of the message
|
||||||
|
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
|
||||||
|
if (flipEndian)
|
||||||
|
Array.Reverse(crcBytes);
|
||||||
|
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] CreateSetMessage(int tuner, String name, String value, uint? lockkey)
|
||||||
|
{
|
||||||
|
var byteName = Encoding.UTF8.GetBytes(String.Format("/tuner{0}/{1}\0", tuner, name));
|
||||||
|
var byteValue = Encoding.UTF8.GetBytes(String.Format("{0}\0", value));
|
||||||
|
|
||||||
|
int messageLength = byteName.Length + byteValue.Length + 12;
|
||||||
|
if (lockkey.HasValue)
|
||||||
|
messageLength += 6;
|
||||||
|
|
||||||
|
var message = new byte[messageLength];
|
||||||
|
|
||||||
|
int offset = InsertHeaderAndName(byteName, messageLength, message);
|
||||||
|
|
||||||
|
bool flipEndian = BitConverter.IsLittleEndian;
|
||||||
|
|
||||||
|
message[offset] = GetSetValue;
|
||||||
|
offset++;
|
||||||
|
message[offset] = Convert.ToByte(byteValue.Length);
|
||||||
|
offset++;
|
||||||
|
Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
|
||||||
|
offset += byteValue.Length;
|
||||||
|
if (lockkey.HasValue)
|
||||||
|
{
|
||||||
|
message[offset] = GetSetLockkey;
|
||||||
|
offset++;
|
||||||
|
message[offset] = (byte)4;
|
||||||
|
offset++;
|
||||||
|
var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
|
||||||
|
if (flipEndian)
|
||||||
|
Array.Reverse(lockKeyBytes);
|
||||||
|
Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate crc and insert at the end of the message
|
||||||
|
var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
|
||||||
|
if (flipEndian)
|
||||||
|
Array.Reverse(crcBytes);
|
||||||
|
Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int InsertHeaderAndName(byte[] byteName, int messageLength, byte[] message)
|
||||||
|
{
|
||||||
|
// check to see if we need to flip endiannes
|
||||||
|
bool flipEndian = BitConverter.IsLittleEndian;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
// create header bytes
|
||||||
|
var getSetBytes = BitConverter.GetBytes(GetSetRequest);
|
||||||
|
var msgLenBytes = BitConverter.GetBytes((ushort)(messageLength - 8)); // Subtrace 4 bytes for header and 4 bytes for crc
|
||||||
|
|
||||||
|
if (flipEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(getSetBytes);
|
||||||
|
Array.Reverse(msgLenBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert header bytes into message
|
||||||
|
Buffer.BlockCopy(getSetBytes, 0, message, offset, 2);
|
||||||
|
offset += 2;
|
||||||
|
Buffer.BlockCopy(msgLenBytes, 0, message, offset, 2);
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
// insert tag name and length
|
||||||
|
message[offset] = GetSetName;
|
||||||
|
offset++;
|
||||||
|
message[offset] = Convert.ToByte(byteName.Length);
|
||||||
|
offset++;
|
||||||
|
|
||||||
|
// insert name string
|
||||||
|
Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
|
||||||
|
offset += byteName.Length;
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ParseReturnMessage(byte[] buf, int numBytes, out string returnVal)
|
||||||
|
{
|
||||||
|
returnVal = String.Empty;
|
||||||
|
|
||||||
|
if (numBytes < 4)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var flipEndian = BitConverter.IsLittleEndian;
|
||||||
|
int offset = 0;
|
||||||
|
byte[] msgTypeBytes = new byte[2];
|
||||||
|
Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
|
||||||
|
|
||||||
|
if (flipEndian)
|
||||||
|
Array.Reverse(msgTypeBytes);
|
||||||
|
|
||||||
|
var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
if (msgType != GetSetReply)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
byte[] msgLengthBytes = new byte[2];
|
||||||
|
Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
|
||||||
|
if (flipEndian)
|
||||||
|
Array.Reverse(msgLengthBytes);
|
||||||
|
|
||||||
|
var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
if (numBytes < msgLength + 8)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var nameTag = buf[offset];
|
||||||
|
offset++;
|
||||||
|
|
||||||
|
var nameLength = buf[offset];
|
||||||
|
offset++;
|
||||||
|
|
||||||
|
// skip the name field to get to value for return
|
||||||
|
offset += nameLength;
|
||||||
|
|
||||||
|
var valueTag = buf[offset];
|
||||||
|
offset++;
|
||||||
|
|
||||||
|
var valueLength = buf[offset];
|
||||||
|
offset++;
|
||||||
|
|
||||||
|
returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HdHomerunCrc
|
||||||
|
{
|
||||||
|
private static UInt32[] crc_table = {
|
||||||
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
||||||
|
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||||
|
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||||
|
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||||
|
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
||||||
|
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||||
|
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||||
|
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||||
|
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||||
|
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||||
|
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
|
||||||
|
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||||
|
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
|
||||||
|
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||||
|
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||||
|
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||||
|
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
|
||||||
|
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||||
|
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
|
||||||
|
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||||
|
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||||
|
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||||
|
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
|
||||||
|
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||||
|
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
||||||
|
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||||
|
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||||
|
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||||
|
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
|
||||||
|
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||||
|
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
|
||||||
|
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||||
|
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||||
|
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||||
|
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||||
|
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||||
|
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
|
||||||
|
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||||
|
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||||
|
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||||
|
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
|
||||||
|
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||||
|
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
|
||||||
|
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||||
|
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||||
|
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||||
|
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
|
||||||
|
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||||
|
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||||
|
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||||
|
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||||
|
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||||
|
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
|
||||||
|
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||||
|
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
||||||
|
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||||
|
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||||
|
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||||
|
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
|
||||||
|
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||||
|
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
|
||||||
|
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||||
|
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||||
|
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d };
|
||||||
|
|
||||||
|
public static UInt32 GetCrc32(byte[] bytes, int numBytes)
|
||||||
|
{
|
||||||
|
var hash = 0xffffffff;
|
||||||
|
for (var i = 0; i < numBytes; i++)
|
||||||
|
hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
|
||||||
|
|
||||||
|
var tmp = ~hash & 0xffffffff;
|
||||||
|
var b0 = tmp & 0xff;
|
||||||
|
var b1 = (tmp >> 8) & 0xff;
|
||||||
|
var b2 = (tmp >> 16) & 0xff;
|
||||||
|
var b3 = (tmp >> 24) & 0xff;
|
||||||
|
hash = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Model.Dto;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
|
||||||
|
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
{
|
||||||
|
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
private readonly ISocketFactory _socketFactory;
|
||||||
|
|
||||||
|
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
private readonly MulticastStream _multicastStream;
|
||||||
|
private readonly IHdHomerunChannelCommands _channelCommands;
|
||||||
|
private readonly int _numTuners;
|
||||||
|
private readonly INetworkManager _networkManager;
|
||||||
|
|
||||||
|
public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager)
|
||||||
|
: base(mediaSource)
|
||||||
|
{
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
_appPaths = appPaths;
|
||||||
|
_appHost = appHost;
|
||||||
|
_socketFactory = socketFactory;
|
||||||
|
_networkManager = networkManager;
|
||||||
|
OriginalStreamId = originalStreamId;
|
||||||
|
_multicastStream = new MulticastStream(_logger);
|
||||||
|
_channelCommands = channelCommands;
|
||||||
|
_numTuners = numTuners;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OpenInternal(CancellationToken openCancellationToken)
|
||||||
|
{
|
||||||
|
_liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var mediaSource = OriginalMediaSource;
|
||||||
|
|
||||||
|
var uri = new Uri(mediaSource.Path);
|
||||||
|
var localPort = _networkManager.GetRandomUnusedUdpPort();
|
||||||
|
|
||||||
|
_logger.Info("Opening HDHR UDP Live stream from {0}", uri.Host);
|
||||||
|
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
|
StartStreaming(uri.Host, localPort, taskCompletionSource, _liveStreamCancellationTokenSource.Token);
|
||||||
|
|
||||||
|
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||||
|
//OpenedMediaSource.Path = tempFile;
|
||||||
|
//OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||||
|
|
||||||
|
OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||||
|
OpenedMediaSource.Protocol = MediaProtocol.Http;
|
||||||
|
OpenedMediaSource.SupportsDirectPlay = false;
|
||||||
|
OpenedMediaSource.SupportsDirectStream = true;
|
||||||
|
OpenedMediaSource.SupportsTranscoding = true;
|
||||||
|
|
||||||
|
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||||
|
|
||||||
|
//await Task.Delay(5000).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Close()
|
||||||
|
{
|
||||||
|
_logger.Info("Closing HDHR UDP live stream");
|
||||||
|
_liveStreamCancellationTokenSource.Cancel();
|
||||||
|
|
||||||
|
return _liveStreamTaskCompletionSource.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var isFirstAttempt = true;
|
||||||
|
using (var udpClient = _socketFactory.CreateUdpSocket(localPort))
|
||||||
|
{
|
||||||
|
using (var hdHomerunManager = new HdHomerunManager(_socketFactory))
|
||||||
|
{
|
||||||
|
var remoteAddress = _networkManager.ParseIpAddress(remoteIp);
|
||||||
|
IpAddressInfo localAddress = null;
|
||||||
|
using (var tcpSocket = _socketFactory.CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, false))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tcpSocket.Connect(new IpEndPointInfo(remoteAddress, HdHomerunManager.HdHomeRunPort));
|
||||||
|
localAddress = tcpSocket.LocalEndPoint.IpAddress;
|
||||||
|
tcpSocket.Close();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_logger.Error("Unable to determine local ip address for Legacy HDHomerun stream.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
Action onStarted = null;
|
||||||
|
if (isFirstAttempt)
|
||||||
|
{
|
||||||
|
onStarted = () => openTaskCompletionSource.TrySetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream = new UdpClientStream(udpClient);
|
||||||
|
await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (isFirstAttempt)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error opening live stream:", ex);
|
||||||
|
openTaskCompletionSource.TrySetException(ex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.ErrorException("Error copying live stream, will reopen", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirstAttempt = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await hdHomerunManager.StopStreaming().ConfigureAwait(false);
|
||||||
|
udpClient.Dispose();
|
||||||
|
_liveStreamTaskCompletionSource.TrySetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _multicastStream.CopyToAsync(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handles the ReadAsync function only of a Stream object
|
||||||
|
// This is used to wrap a UDP socket into a stream for MulticastStream which only uses ReadAsync
|
||||||
|
public class UdpClientStream : Stream
|
||||||
|
{
|
||||||
|
private static int RtpHeaderBytes = 12;
|
||||||
|
private static int PacketSize = 1316;
|
||||||
|
private readonly ISocket _udpClient;
|
||||||
|
bool disposed;
|
||||||
|
|
||||||
|
public UdpClientStream(ISocket udpClient) : base()
|
||||||
|
{
|
||||||
|
_udpClient = udpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
throw new ArgumentNullException("buffer");
|
||||||
|
|
||||||
|
if (offset + count < 0)
|
||||||
|
throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count");
|
||||||
|
|
||||||
|
if (offset + count > buffer.Length)
|
||||||
|
throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count");
|
||||||
|
|
||||||
|
if (disposed)
|
||||||
|
throw new ObjectDisposedException(typeof(UdpClientStream).ToString());
|
||||||
|
|
||||||
|
// This will always receive a 1328 packet size (PacketSize + RtpHeaderSize)
|
||||||
|
// The RTP header will be stripped so see how many reads we need to make to fill the buffer.
|
||||||
|
int numReads = count / PacketSize;
|
||||||
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < numReads; ++i)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
}
|
||||||
|
return totalBytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanSeek
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
Id = channel.Path.GetMD5().ToString("N"),
|
Id = channel.Path.GetMD5().ToString("N"),
|
||||||
IsInfiniteStream = true,
|
IsInfiniteStream = true,
|
||||||
SupportsDirectStream = false,
|
|
||||||
IsRemote = true
|
IsRemote = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
private const int BufferSize = 81920;
|
private const int BufferSize = 81920;
|
||||||
private CancellationToken _cancellationToken;
|
private CancellationToken _cancellationToken;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ConcurrentQueue<byte[]> _sharedBuffer = new ConcurrentQueue<byte[]>();
|
|
||||||
|
|
||||||
public MulticastStream(ILogger logger)
|
public MulticastStream(ILogger logger)
|
||||||
{
|
{
|
||||||
|
@ -38,14 +37,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
byte[] copy = new byte[bytesRead];
|
byte[] copy = new byte[bytesRead];
|
||||||
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
|
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
|
||||||
|
|
||||||
_sharedBuffer.Enqueue(copy);
|
|
||||||
|
|
||||||
while (_sharedBuffer.Count > 3000)
|
|
||||||
{
|
|
||||||
byte[] bytes;
|
|
||||||
_sharedBuffer.TryDequeue(out bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
var allStreams = _outputStreams.ToList();
|
var allStreams = _outputStreams.ToList();
|
||||||
foreach (var stream in allStreams)
|
foreach (var stream in allStreams)
|
||||||
{
|
{
|
||||||
|
@ -74,16 +65,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
OnFinished = OnFinished
|
OnFinished = OnFinished
|
||||||
};
|
};
|
||||||
|
|
||||||
var list = new List<byte>();
|
|
||||||
foreach (var bytes in _sharedBuffer)
|
|
||||||
{
|
|
||||||
list.AddRange(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Info("QueueStream started with {0} initial bytes", list.Count);
|
|
||||||
|
|
||||||
result.Queue(list.ToArray());
|
|
||||||
|
|
||||||
_outputStreams.TryAdd(result.Id, result);
|
_outputStreams.TryAdd(result.Id, result);
|
||||||
|
|
||||||
result.Start(_cancellationToken);
|
result.Start(_cancellationToken);
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Sorting;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Sorting
|
|
||||||
{
|
|
||||||
public class BudgetComparer : IBaseItemComparer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Compares the specified x.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="x">The x.</param>
|
|
||||||
/// <param name="y">The y.</param>
|
|
||||||
/// <returns>System.Int32.</returns>
|
|
||||||
public int Compare(BaseItem x, BaseItem y)
|
|
||||||
{
|
|
||||||
return GetValue(x).CompareTo(GetValue(y));
|
|
||||||
}
|
|
||||||
|
|
||||||
private double GetValue(BaseItem x)
|
|
||||||
{
|
|
||||||
var hasBudget = x as IHasBudget;
|
|
||||||
if (hasBudget != null)
|
|
||||||
{
|
|
||||||
return hasBudget.Budget ?? 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get { return ItemSortBy.Budget; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Sorting;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Sorting
|
|
||||||
{
|
|
||||||
public class RevenueComparer : IBaseItemComparer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Compares the specified x.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="x">The x.</param>
|
|
||||||
/// <param name="y">The y.</param>
|
|
||||||
/// <returns>System.Int32.</returns>
|
|
||||||
public int Compare(BaseItem x, BaseItem y)
|
|
||||||
{
|
|
||||||
return GetValue(x).CompareTo(GetValue(y));
|
|
||||||
}
|
|
||||||
|
|
||||||
private double GetValue(BaseItem x)
|
|
||||||
{
|
|
||||||
var hasBudget = x as IHasBudget;
|
|
||||||
if (hasBudget != null)
|
|
||||||
{
|
|
||||||
return hasBudget.Revenue ?? 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get { return ItemSortBy.Revenue; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.Udp
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _udp client
|
/// The _udp client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IUdpSocket _udpClient;
|
private ISocket _udpClient;
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.Udp
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _udpClient.ReceiveAsync().ConfigureAwait(false);
|
var result = await _udpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
OnMessageReceived(result);
|
OnMessageReceived(result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,15 +162,15 @@ namespace Emby.Server.Implementations.Updates
|
||||||
string packageType = null,
|
string packageType = null,
|
||||||
Version applicationVersion = null)
|
Version applicationVersion = null)
|
||||||
{
|
{
|
||||||
var data = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "key", _securityManager.SupporterKey },
|
|
||||||
{ "mac", _applicationHost.SystemId },
|
|
||||||
{ "systemid", _applicationHost.SystemId }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (withRegistration)
|
if (withRegistration)
|
||||||
{
|
{
|
||||||
|
var data = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "key", _securityManager.SupporterKey },
|
||||||
|
{ "mac", _applicationHost.SystemId },
|
||||||
|
{ "systemid", _applicationHost.SystemId }
|
||||||
|
};
|
||||||
|
|
||||||
using (var json = await _httpClient.Post("https://www.mb3admin.com/admin/service/package/retrieveall", data, cancellationToken).ConfigureAwait(false))
|
using (var json = await _httpClient.Post("https://www.mb3admin.com/admin/service/package/retrieveall", data, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
@ -353,7 +353,7 @@ namespace Emby.Server.Implementations.Updates
|
||||||
/// <returns>Task{PackageVersionInfo}.</returns>
|
/// <returns>Task{PackageVersionInfo}.</returns>
|
||||||
public async Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version)
|
public async Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version)
|
||||||
{
|
{
|
||||||
var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false);
|
var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false);
|
||||||
|
|
||||||
var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase))
|
var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase))
|
||||||
?? packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
?? packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
@ -376,7 +376,7 @@ namespace Emby.Server.Implementations.Updates
|
||||||
/// <returns>Task{PackageVersionInfo}.</returns>
|
/// <returns>Task{PackageVersionInfo}.</returns>
|
||||||
public async Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release)
|
public async Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release)
|
||||||
{
|
{
|
||||||
var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false);
|
var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false);
|
||||||
|
|
||||||
return GetLatestCompatibleVersion(packages, name, guid, currentServerVersion, classification);
|
return GetLatestCompatibleVersion(packages, name, guid, currentServerVersion, classification);
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,10 +134,6 @@
|
||||||
<Compile Include="SearchService.cs" />
|
<Compile Include="SearchService.cs" />
|
||||||
<Compile Include="Session\SessionsService.cs" />
|
<Compile Include="Session\SessionsService.cs" />
|
||||||
<Compile Include="SimilarItemsHelper.cs" />
|
<Compile Include="SimilarItemsHelper.cs" />
|
||||||
<Compile Include="Sync\SyncHelper.cs" />
|
|
||||||
<Compile Include="Sync\SyncJobWebSocketListener.cs" />
|
|
||||||
<Compile Include="Sync\SyncJobsWebSocketListener.cs" />
|
|
||||||
<Compile Include="Sync\SyncService.cs" />
|
|
||||||
<Compile Include="System\ActivityLogService.cs" />
|
<Compile Include="System\ActivityLogService.cs" />
|
||||||
<Compile Include="System\ActivityLogWebSocketListener.cs" />
|
<Compile Include="System\ActivityLogWebSocketListener.cs" />
|
||||||
<Compile Include="System\SystemService.cs" />
|
<Compile Include="System\SystemService.cs" />
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class AudioService
|
/// Class AudioService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authenticated]
|
||||||
public class AudioService : BaseProgressiveStreamingService
|
public class AudioService : BaseProgressiveStreamingService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
using MediaBrowser.Model.Dto;
|
|
||||||
using MediaBrowser.Model.Sync;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Sync
|
|
||||||
{
|
|
||||||
public static class SyncHelper
|
|
||||||
{
|
|
||||||
public static List<SyncJobOption> GetSyncOptions(List<BaseItemDto> items)
|
|
||||||
{
|
|
||||||
List<SyncJobOption> options = new List<SyncJobOption>();
|
|
||||||
|
|
||||||
foreach (BaseItemDto item in items)
|
|
||||||
{
|
|
||||||
if (item.SupportsSync ?? false)
|
|
||||||
{
|
|
||||||
if (item.IsVideo)
|
|
||||||
{
|
|
||||||
options.Add(SyncJobOption.Quality);
|
|
||||||
options.Add(SyncJobOption.Profile);
|
|
||||||
if (items.Count > 1)
|
|
||||||
{
|
|
||||||
options.Add(SyncJobOption.UnwatchedOnly);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (item.IsAudio)
|
|
||||||
{
|
|
||||||
options.Add(SyncJobOption.Quality);
|
|
||||||
options.Add(SyncJobOption.Profile);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (item.IsMusicGenre || item.IsArtist|| item.IsType("musicalbum"))
|
|
||||||
{
|
|
||||||
options.Add(SyncJobOption.Quality);
|
|
||||||
options.Add(SyncJobOption.Profile);
|
|
||||||
options.Add(SyncJobOption.ItemLimit);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((item.IsFolder ?? false) && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
|
|
||||||
{
|
|
||||||
options.Add(SyncJobOption.Quality);
|
|
||||||
options.Add(SyncJobOption.Profile);
|
|
||||||
options.Add(SyncJobOption.UnwatchedOnly);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (item.IsGenre)
|
|
||||||
{
|
|
||||||
options.Add(SyncJobOption.SyncNewContent);
|
|
||||||
options.Add(SyncJobOption.ItemLimit);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (BaseItemDto item in items)
|
|
||||||
{
|
|
||||||
if (item.SupportsSync ?? false)
|
|
||||||
{
|
|
||||||
if ((item.IsFolder ?? false) || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson)
|
|
||||||
{
|
|
||||||
options.Add(SyncJobOption.SyncNewContent);
|
|
||||||
options.Add(SyncJobOption.ItemLimit);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<SyncJobOption> GetSyncOptions(SyncCategory category)
|
|
||||||
{
|
|
||||||
List<SyncJobOption> options = new List<SyncJobOption>();
|
|
||||||
|
|
||||||
options.Add(SyncJobOption.Quality);
|
|
||||||
options.Add(SyncJobOption.Profile);
|
|
||||||
options.Add(SyncJobOption.UnwatchedOnly);
|
|
||||||
options.Add(SyncJobOption.SyncNewContent);
|
|
||||||
options.Add(SyncJobOption.ItemLimit);
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
using MediaBrowser.Controller.Sync;
|
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using MediaBrowser.Model.Sync;
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.Threading;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Sync
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class SessionInfoWebSocketListener
|
|
||||||
/// </summary>
|
|
||||||
class SyncJobWebSocketListener : BasePeriodicWebSocketListener<CompleteSyncJobInfo, WebSocketListenerState>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
protected override string Name
|
|
||||||
{
|
|
||||||
get { return "SyncJob"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ISyncManager _syncManager;
|
|
||||||
private string _jobId;
|
|
||||||
|
|
||||||
public SyncJobWebSocketListener(ILogger logger, ISyncManager syncManager, ITimerFactory timerFactory)
|
|
||||||
: base(logger, timerFactory)
|
|
||||||
{
|
|
||||||
_syncManager = syncManager;
|
|
||||||
_syncManager.SyncJobCancelled += _syncManager_SyncJobCancelled;
|
|
||||||
_syncManager.SyncJobUpdated += _syncManager_SyncJobUpdated;
|
|
||||||
_syncManager.SyncJobItemCreated += _syncManager_SyncJobItemCreated;
|
|
||||||
_syncManager.SyncJobItemUpdated += _syncManager_SyncJobItemUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncManager_SyncJobItemUpdated(object sender, GenericEventArgs<SyncJobItem> e)
|
|
||||||
{
|
|
||||||
if (string.Equals(e.Argument.Id, _jobId, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
SendData(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncManager_SyncJobItemCreated(object sender, GenericEventArgs<SyncJobItem> e)
|
|
||||||
{
|
|
||||||
if (string.Equals(e.Argument.Id, _jobId, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
SendData(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ParseMessageParams(string[] values)
|
|
||||||
{
|
|
||||||
base.ParseMessageParams(values);
|
|
||||||
|
|
||||||
if (values.Length > 0)
|
|
||||||
{
|
|
||||||
_jobId = values[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncManager_SyncJobUpdated(object sender, GenericEventArgs<SyncJob> e)
|
|
||||||
{
|
|
||||||
if (string.Equals(e.Argument.Id, _jobId, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
SendData(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncManager_SyncJobCancelled(object sender, GenericEventArgs<SyncJob> e)
|
|
||||||
{
|
|
||||||
if (string.Equals(e.Argument.Id, _jobId, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
SendData(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data to send.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">The state.</param>
|
|
||||||
/// <returns>Task{SystemInfo}.</returns>
|
|
||||||
protected override Task<CompleteSyncJobInfo> GetDataToSend(WebSocketListenerState state)
|
|
||||||
{
|
|
||||||
var job = _syncManager.GetJob(_jobId);
|
|
||||||
var items = _syncManager.GetJobItems(new SyncJobItemQuery
|
|
||||||
{
|
|
||||||
AddMetadata = true,
|
|
||||||
JobId = _jobId
|
|
||||||
});
|
|
||||||
|
|
||||||
var info = new CompleteSyncJobInfo
|
|
||||||
{
|
|
||||||
Job = job,
|
|
||||||
JobItems = items.Items.ToList()
|
|
||||||
};
|
|
||||||
|
|
||||||
return Task.FromResult(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool SendOnTimer
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool dispose)
|
|
||||||
{
|
|
||||||
_syncManager.SyncJobCancelled -= _syncManager_SyncJobCancelled;
|
|
||||||
_syncManager.SyncJobUpdated -= _syncManager_SyncJobUpdated;
|
|
||||||
|
|
||||||
base.Dispose(dispose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
using MediaBrowser.Controller.Sync;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using MediaBrowser.Model.Sync;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.Threading;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Sync
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class SessionInfoWebSocketListener
|
|
||||||
/// </summary>
|
|
||||||
class SyncJobsWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SyncJob>, WebSocketListenerState>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
protected override string Name
|
|
||||||
{
|
|
||||||
get { return "SyncJobs"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ISyncManager _syncManager;
|
|
||||||
private string _userId;
|
|
||||||
private string _targetId;
|
|
||||||
|
|
||||||
public SyncJobsWebSocketListener(ILogger logger, ISyncManager syncManager, ITimerFactory timerFactory)
|
|
||||||
: base(logger, timerFactory)
|
|
||||||
{
|
|
||||||
_syncManager = syncManager;
|
|
||||||
_syncManager.SyncJobCancelled += _syncManager_SyncJobCancelled;
|
|
||||||
_syncManager.SyncJobCreated += _syncManager_SyncJobCreated;
|
|
||||||
_syncManager.SyncJobUpdated += _syncManager_SyncJobUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ParseMessageParams(string[] values)
|
|
||||||
{
|
|
||||||
base.ParseMessageParams(values);
|
|
||||||
|
|
||||||
if (values.Length > 0)
|
|
||||||
{
|
|
||||||
_userId = values[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.Length > 1)
|
|
||||||
{
|
|
||||||
_targetId = values[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncManager_SyncJobUpdated(object sender, Model.Events.GenericEventArgs<SyncJob> e)
|
|
||||||
{
|
|
||||||
SendData(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncManager_SyncJobCreated(object sender, Model.Events.GenericEventArgs<SyncJobCreationResult> e)
|
|
||||||
{
|
|
||||||
SendData(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncManager_SyncJobCancelled(object sender, Model.Events.GenericEventArgs<SyncJob> e)
|
|
||||||
{
|
|
||||||
SendData(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data to send.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state">The state.</param>
|
|
||||||
/// <returns>Task{SystemInfo}.</returns>
|
|
||||||
protected override async Task<IEnumerable<SyncJob>> GetDataToSend(WebSocketListenerState state)
|
|
||||||
{
|
|
||||||
var jobs = await _syncManager.GetJobs(new SyncJobQuery
|
|
||||||
{
|
|
||||||
UserId = _userId,
|
|
||||||
TargetId = _targetId
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return jobs.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool SendOnTimer
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool dispose)
|
|
||||||
{
|
|
||||||
_syncManager.SyncJobCancelled -= _syncManager_SyncJobCancelled;
|
|
||||||
_syncManager.SyncJobCreated -= _syncManager_SyncJobCreated;
|
|
||||||
_syncManager.SyncJobUpdated -= _syncManager_SyncJobUpdated;
|
|
||||||
|
|
||||||
base.Dispose(dispose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,396 +0,0 @@
|
||||||
using MediaBrowser.Common.Extensions;
|
|
||||||
using MediaBrowser.Controller.Dto;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Controller.Sync;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
using MediaBrowser.Model.Sync;
|
|
||||||
using MediaBrowser.Model.Users;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Sync
|
|
||||||
{
|
|
||||||
[Route("/Sync/Jobs/{Id}", "DELETE", Summary = "Cancels a sync job.")]
|
|
||||||
public class CancelSyncJob : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Jobs/{Id}", "GET", Summary = "Gets a sync job.")]
|
|
||||||
public class GetSyncJob : IReturn<SyncJob>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Jobs/{Id}", "POST", Summary = "Updates a sync job.")]
|
|
||||||
public class UpdateSyncJob : SyncJob, IReturnVoid
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/JobItems", "GET", Summary = "Gets sync job items.")]
|
|
||||||
public class GetSyncJobItems : SyncJobItemQuery, IReturn<QueryResult<SyncJobItem>>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/JobItems/{Id}/Enable", "POST", Summary = "Enables a cancelled or queued sync job item")]
|
|
||||||
public class EnableSyncJobItem : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/JobItems/{Id}/MarkForRemoval", "POST", Summary = "Marks a job item for removal")]
|
|
||||||
public class MarkJobItemForRemoval : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/JobItems/{Id}/UnmarkForRemoval", "POST", Summary = "Unmarks a job item for removal")]
|
|
||||||
public class UnmarkJobItemForRemoval : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/JobItems/{Id}", "DELETE", Summary = "Cancels a sync job item")]
|
|
||||||
public class CancelSyncJobItem : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Items/Cancel", "POST", Summary = "Cancels items from a sync target")]
|
|
||||||
[Route("/Sync/{TargetId}/Items", "DELETE", Summary = "Cancels items from a sync target")]
|
|
||||||
public class CancelItems : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "TargetId", Description = "TargetId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "Items")]
|
|
||||||
public string TargetId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "ItemIds", Description = "ItemIds", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "Items")]
|
|
||||||
public string ItemIds { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Jobs", "GET", Summary = "Gets sync jobs.")]
|
|
||||||
public class GetSyncJobs : SyncJobQuery, IReturn<QueryResult<SyncJob>>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Jobs", "POST", Summary = "Gets sync jobs.")]
|
|
||||||
public class CreateSyncJob : SyncJobRequest, IReturn<SyncJobCreationResult>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Targets", "GET", Summary = "Gets a list of available sync targets.")]
|
|
||||||
public class GetSyncTargets : IReturn<List<SyncTarget>>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string UserId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Options", "GET", Summary = "Gets a list of available sync targets.")]
|
|
||||||
public class GetSyncDialogOptions : IReturn<SyncDialogOptions>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string UserId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "ItemIds", Description = "ItemIds", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string ItemIds { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "ParentId", Description = "ParentId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string ParentId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "TargetId", Description = "TargetId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string TargetId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Category", Description = "Category", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public SyncCategory? Category { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/JobItems/{Id}/Transferred", "POST", Summary = "Reports that a sync job item has successfully been transferred.")]
|
|
||||||
public class ReportSyncJobItemTransferred : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/JobItems/{Id}/File", "GET", Summary = "Gets a sync job item file")]
|
|
||||||
public class GetSyncJobItemFile
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/JobItems/{Id}/AdditionalFiles", "GET", Summary = "Gets a sync job item file")]
|
|
||||||
public class GetSyncJobItemAdditionalFile
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/OfflineActions", "POST", Summary = "Reports an action that occurred while offline.")]
|
|
||||||
public class ReportOfflineActions : List<UserAction>, IReturnVoid
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Items/Ready", "GET", Summary = "Gets ready to download sync items.")]
|
|
||||||
public class GetReadySyncItems : IReturn<List<SyncedItem>>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "TargetId", Description = "TargetId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string TargetId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Sync/Data", "POST", Summary = "Syncs data between device and server")]
|
|
||||||
public class SyncData : SyncDataRequest, IReturn<SyncDataResponse>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authenticated]
|
|
||||||
public class SyncService : BaseApiService
|
|
||||||
{
|
|
||||||
private readonly ISyncManager _syncManager;
|
|
||||||
private readonly IDtoService _dtoService;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
private readonly IAuthorizationContext _authContext;
|
|
||||||
|
|
||||||
public SyncService(ISyncManager syncManager, IDtoService dtoService, ILibraryManager libraryManager, IUserManager userManager, IAuthorizationContext authContext)
|
|
||||||
{
|
|
||||||
_syncManager = syncManager;
|
|
||||||
_dtoService = dtoService;
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_userManager = userManager;
|
|
||||||
_authContext = authContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetSyncTargets request)
|
|
||||||
{
|
|
||||||
var result = _syncManager.GetSyncTargets(request.UserId);
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Get(GetSyncJobs request)
|
|
||||||
{
|
|
||||||
var result = await _syncManager.GetJobs(request).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetSyncJobItems request)
|
|
||||||
{
|
|
||||||
var result = _syncManager.GetJobItems(request);
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetSyncJob request)
|
|
||||||
{
|
|
||||||
var result = _syncManager.GetJob(request.Id);
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(CancelSyncJob request)
|
|
||||||
{
|
|
||||||
var task = _syncManager.CancelJob(request.Id);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Post(CreateSyncJob request)
|
|
||||||
{
|
|
||||||
var result = await _syncManager.CreateJob(request).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Any(CancelItems request)
|
|
||||||
{
|
|
||||||
var itemIds = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
var task = _syncManager.CancelItems(request.TargetId, itemIds);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(ReportSyncJobItemTransferred request)
|
|
||||||
{
|
|
||||||
var task = _syncManager.ReportSyncJobItemTransferred(request.Id);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Get(GetSyncJobItemFile request)
|
|
||||||
{
|
|
||||||
var jobItem = _syncManager.GetJobItem(request.Id);
|
|
||||||
|
|
||||||
if (jobItem == null)
|
|
||||||
{
|
|
||||||
throw new ResourceNotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jobItem.Status < SyncJobItemStatus.ReadyToTransfer)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("The job item is not yet ready for transfer.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _syncManager.ReportSyncJobItemTransferBeginning(request.Id).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
|
||||||
{
|
|
||||||
Path = jobItem.OutputPath,
|
|
||||||
OnError = () =>
|
|
||||||
{
|
|
||||||
var failedTask = _syncManager.ReportSyncJobItemTransferFailed(request.Id);
|
|
||||||
Task.WaitAll(failedTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Get(GetSyncDialogOptions request)
|
|
||||||
{
|
|
||||||
var result = new SyncDialogOptions();
|
|
||||||
|
|
||||||
result.Targets = _syncManager.GetSyncTargets(request.UserId)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var auth = _authContext.GetAuthorizationInfo(Request);
|
|
||||||
var authenticatedUser = _userManager.GetUserById(auth.UserId);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.TargetId))
|
|
||||||
{
|
|
||||||
result.Targets = result.Targets
|
|
||||||
.Where(i => string.Equals(i.Id, request.TargetId, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
result.QualityOptions = _syncManager
|
|
||||||
.GetQualityOptions(request.TargetId, authenticatedUser)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
result.ProfileOptions = _syncManager
|
|
||||||
.GetProfileOptions(request.TargetId, authenticatedUser)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.Category.HasValue)
|
|
||||||
{
|
|
||||||
result.Options = SyncHelper.GetSyncOptions(request.Category.Value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var dtoOptions = new DtoOptions
|
|
||||||
{
|
|
||||||
Fields = new List<ItemFields>
|
|
||||||
{
|
|
||||||
ItemFields.SyncInfo,
|
|
||||||
ItemFields.BasicSyncInfo
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var items = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(_libraryManager.GetItemById)
|
|
||||||
.Where(i => i != null);
|
|
||||||
|
|
||||||
var dtos = (await _dtoService.GetBaseItemDtos(items, dtoOptions, authenticatedUser).ConfigureAwait(false));
|
|
||||||
|
|
||||||
result.Options = SyncHelper.GetSyncOptions(dtos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(ReportOfflineActions request)
|
|
||||||
{
|
|
||||||
var task = PostAsync(request);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PostAsync(ReportOfflineActions request)
|
|
||||||
{
|
|
||||||
foreach (var action in request)
|
|
||||||
{
|
|
||||||
await _syncManager.ReportOfflineAction(action).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Get(GetReadySyncItems request)
|
|
||||||
{
|
|
||||||
var result = await _syncManager.GetReadySyncItems(request.TargetId).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return ToOptimizedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Post(SyncData request)
|
|
||||||
{
|
|
||||||
var response = await _syncManager.SyncData(request).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return ToOptimizedResult(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(UpdateSyncJob request)
|
|
||||||
{
|
|
||||||
var task = _syncManager.UpdateJob(request);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<object> Get(GetSyncJobItemAdditionalFile request)
|
|
||||||
{
|
|
||||||
var jobItem = _syncManager.GetJobItem(request.Id);
|
|
||||||
|
|
||||||
if (jobItem.Status < SyncJobItemStatus.ReadyToTransfer)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("The job item is not yet ready for transfer.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = jobItem.AdditionalFiles.FirstOrDefault(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (file == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Sync job additional file not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, file.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(EnableSyncJobItem request)
|
|
||||||
{
|
|
||||||
var task = _syncManager.ReEnableJobItem(request.Id);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(CancelSyncJobItem request)
|
|
||||||
{
|
|
||||||
var task = _syncManager.CancelJobItem(request.Id);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(MarkJobItemForRemoval request)
|
|
||||||
{
|
|
||||||
var task = _syncManager.MarkJobItemForRemoval(request.Id);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(UnmarkJobItemForRemoval request)
|
|
||||||
{
|
|
||||||
var task = _syncManager.UnmarkJobItemForRemoval(request.Id);
|
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,9 @@ namespace MediaBrowser.Common.Net
|
||||||
/// Gets a random port number that is currently available
|
/// Gets a random port number that is currently available
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>System.Int32.</returns>
|
/// <returns>System.Int32.</returns>
|
||||||
int GetRandomUnusedPort();
|
int GetRandomUnusedTcpPort();
|
||||||
|
|
||||||
|
int GetRandomUnusedUdpPort();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns MAC Address from first Network Card in Computer
|
/// Returns MAC Address from first Network Card in Computer
|
||||||
|
|
|
@ -756,11 +756,6 @@ namespace MediaBrowser.Controller.Entities
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.AiredEpisodeOrder");
|
Logger.Debug("Query requires post-filtering due to ItemSortBy.AiredEpisodeOrder");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (query.SortBy.Contains(ItemSortBy.Budget, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.Budget");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (query.SortBy.Contains(ItemSortBy.GameSystem, StringComparer.OrdinalIgnoreCase))
|
if (query.SortBy.Contains(ItemSortBy.GameSystem, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.GameSystem");
|
Logger.Debug("Query requires post-filtering due to ItemSortBy.GameSystem");
|
||||||
|
@ -776,11 +771,6 @@ namespace MediaBrowser.Controller.Entities
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.Players");
|
Logger.Debug("Query requires post-filtering due to ItemSortBy.Players");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (query.SortBy.Contains(ItemSortBy.Revenue, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
|
if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
|
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
|
||||||
{
|
|
||||||
public interface IHasBudget
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the budget.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The budget.</value>
|
|
||||||
double? Budget { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the revenue.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The revenue.</value>
|
|
||||||
double? Revenue { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class Movie
|
/// Class Movie
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Movie : Video, IHasSpecialFeatures, IHasBudget, IHasTrailers, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
|
public class Movie : Video, IHasSpecialFeatures, IHasTrailers, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
|
||||||
{
|
{
|
||||||
public List<Guid> SpecialFeatureIds { get; set; }
|
public List<Guid> SpecialFeatureIds { get; set; }
|
||||||
|
|
||||||
|
@ -45,18 +45,6 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||||
/// <value>The taglines.</value>
|
/// <value>The taglines.</value>
|
||||||
public List<string> Taglines { get; set; }
|
public List<string> Taglines { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the budget.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The budget.</value>
|
|
||||||
public double? Budget { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the revenue.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The revenue.</value>
|
|
||||||
public double? Revenue { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name of the TMDB collection.
|
/// Gets or sets the name of the TMDB collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -6,19 +6,8 @@ using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasBudget, IHasLookupInfo<MusicVideoInfo>
|
public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasLookupInfo<MusicVideoInfo>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the budget.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The budget.</value>
|
|
||||||
public double? Budget { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the revenue.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The revenue.</value>
|
|
||||||
public double? Revenue { get; set; }
|
|
||||||
public List<string> Artists { get; set; }
|
public List<string> Artists { get; set; }
|
||||||
|
|
||||||
public MusicVideo()
|
public MusicVideo()
|
||||||
|
|
|
@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
|
|
||||||
if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
|
if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
|
||||||
{
|
{
|
||||||
return number.ToString("00000-") + (Name ?? string.Empty);
|
return string.Format("{0:00000.0}", number) + "-" + (Name ?? string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,6 @@
|
||||||
<Compile Include="Entities\GameGenre.cs" />
|
<Compile Include="Entities\GameGenre.cs" />
|
||||||
<Compile Include="Entities\GameSystem.cs" />
|
<Compile Include="Entities\GameSystem.cs" />
|
||||||
<Compile Include="Entities\IHasAspectRatio.cs" />
|
<Compile Include="Entities\IHasAspectRatio.cs" />
|
||||||
<Compile Include="Entities\IHasBudget.cs" />
|
|
||||||
<Compile Include="Entities\IHasDisplayOrder.cs" />
|
<Compile Include="Entities\IHasDisplayOrder.cs" />
|
||||||
<Compile Include="Entities\IHasId.cs" />
|
<Compile Include="Entities\IHasId.cs" />
|
||||||
<Compile Include="Entities\IHasImages.cs" />
|
<Compile Include="Entities\IHasImages.cs" />
|
||||||
|
|
|
@ -78,14 +78,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken);
|
Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the probe size argument.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="inputFiles">The input files.</param>
|
|
||||||
/// <param name="protocol">The protocol.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
string GetProbeSizeAndAnalyzeDurationArgument(string[] inputFiles, MediaProtocol protocol);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the input argument.
|
/// Gets the input argument.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
public IIsoMount MountedIso { get; set; }
|
public IIsoMount MountedIso { get; set; }
|
||||||
public VideoType VideoType { get; set; }
|
public VideoType VideoType { get; set; }
|
||||||
public List<string> PlayableStreamFileNames { get; set; }
|
public List<string> PlayableStreamFileNames { get; set; }
|
||||||
public int AnalyzeDurationSections { get; set; }
|
public int AnalyzeDurationMs { get; set; }
|
||||||
|
|
||||||
public MediaInfoRequest()
|
public MediaInfoRequest()
|
||||||
{
|
{
|
||||||
|
|
|
@ -177,7 +177,7 @@ namespace MediaBrowser.LocalMetadata.Images
|
||||||
"default"
|
"default"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (item is MusicAlbum || item is MusicArtist || item is PhotoAlbum)
|
if (item is MusicAlbum || item is MusicArtist || item is PhotoAlbum || item is Person)
|
||||||
{
|
{
|
||||||
// these prefer folder
|
// these prefer folder
|
||||||
names.Insert(0, "poster");
|
names.Insert(0, "poster");
|
||||||
|
|
|
@ -207,38 +207,6 @@ namespace MediaBrowser.LocalMetadata.Parsers
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "Budget":
|
|
||||||
{
|
|
||||||
var text = reader.ReadElementContentAsString();
|
|
||||||
var hasBudget = item as IHasBudget;
|
|
||||||
if (hasBudget != null)
|
|
||||||
{
|
|
||||||
double value;
|
|
||||||
if (double.TryParse(text, NumberStyles.Any, _usCulture, out value))
|
|
||||||
{
|
|
||||||
hasBudget.Budget = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "Revenue":
|
|
||||||
{
|
|
||||||
var text = reader.ReadElementContentAsString();
|
|
||||||
var hasBudget = item as IHasBudget;
|
|
||||||
if (hasBudget != null)
|
|
||||||
{
|
|
||||||
double value;
|
|
||||||
if (double.TryParse(text, NumberStyles.Any, _usCulture, out value))
|
|
||||||
{
|
|
||||||
hasBudget.Revenue = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "Metascore":
|
case "Metascore":
|
||||||
{
|
{
|
||||||
var text = reader.ReadElementContentAsString();
|
var text = reader.ReadElementContentAsString();
|
||||||
|
|
|
@ -37,7 +37,6 @@ namespace MediaBrowser.LocalMetadata.Savers
|
||||||
"AudioDbArtistId",
|
"AudioDbArtistId",
|
||||||
"AwardSummary",
|
"AwardSummary",
|
||||||
"BirthDate",
|
"BirthDate",
|
||||||
"Budget",
|
|
||||||
|
|
||||||
// Deprecated. No longer saving in this field.
|
// Deprecated. No longer saving in this field.
|
||||||
"certification",
|
"certification",
|
||||||
|
@ -90,7 +89,6 @@ namespace MediaBrowser.LocalMetadata.Savers
|
||||||
"PremiereDate",
|
"PremiereDate",
|
||||||
"ProductionYear",
|
"ProductionYear",
|
||||||
"Rating",
|
"Rating",
|
||||||
"Revenue",
|
|
||||||
"RottenTomatoesId",
|
"RottenTomatoesId",
|
||||||
"RunningTime",
|
"RunningTime",
|
||||||
|
|
||||||
|
@ -435,20 +433,6 @@ namespace MediaBrowser.LocalMetadata.Savers
|
||||||
writer.WriteElementString("AwardSummary", hasAwards.AwardSummary);
|
writer.WriteElementString("AwardSummary", hasAwards.AwardSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasBudget = item as IHasBudget;
|
|
||||||
if (hasBudget != null)
|
|
||||||
{
|
|
||||||
if (hasBudget.Budget.HasValue)
|
|
||||||
{
|
|
||||||
writer.WriteElementString("Budget", hasBudget.Budget.Value.ToString(UsCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasBudget.Revenue.HasValue)
|
|
||||||
{
|
|
||||||
writer.WriteElementString("Revenue", hasBudget.Revenue.Value.ToString(UsCulture));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.CommunityRating.HasValue)
|
if (item.CommunityRating.HasValue)
|
||||||
{
|
{
|
||||||
writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(UsCulture));
|
writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(UsCulture));
|
||||||
|
|
|
@ -90,6 +90,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
var required = new[]
|
var required = new[]
|
||||||
{
|
{
|
||||||
"h264_qsv",
|
"h264_qsv",
|
||||||
|
"hevc_qsv",
|
||||||
"mpeg2_qsv",
|
"mpeg2_qsv",
|
||||||
"vc1_qsv"
|
"vc1_qsv"
|
||||||
};
|
};
|
||||||
|
@ -134,9 +135,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
"libvorbis",
|
"libvorbis",
|
||||||
"srt",
|
"srt",
|
||||||
"h264_nvenc",
|
"h264_nvenc",
|
||||||
|
"hevc_nvenc",
|
||||||
"h264_qsv",
|
"h264_qsv",
|
||||||
|
"hevc_qsv",
|
||||||
"h264_omx",
|
"h264_omx",
|
||||||
|
"hevc_omx",
|
||||||
"h264_vaapi",
|
"h264_vaapi",
|
||||||
|
"hevc_vaapi",
|
||||||
"ac3"
|
"ac3"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -61,15 +61,5 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
// Quotes are valid path characters in linux and they need to be escaped here with a leading \
|
// Quotes are valid path characters in linux and they need to be escaped here with a leading \
|
||||||
return path.Replace("\"", "\\\"");
|
return path.Replace("\"", "\\\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetProbeSizeArgument(int numInputFiles)
|
|
||||||
{
|
|
||||||
return numInputFiles > 1 ? "-probesize 1G" : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetAnalyzeDurationArgument(int numInputFiles)
|
|
||||||
{
|
|
||||||
return numInputFiles > 1 ? "-analyzeduration 200M" : "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -523,17 +523,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames);
|
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames);
|
||||||
|
|
||||||
var probeSize = EncodingUtils.GetProbeSizeArgument(inputFiles.Length);
|
var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length);
|
||||||
string analyzeDuration;
|
string analyzeDuration;
|
||||||
|
|
||||||
if (request.AnalyzeDurationSections > 0)
|
if (request.AnalyzeDurationMs > 0)
|
||||||
{
|
{
|
||||||
analyzeDuration = "-analyzeduration " +
|
analyzeDuration = "-analyzeduration " +
|
||||||
(request.AnalyzeDurationSections * 1000000).ToString(CultureInfo.InvariantCulture);
|
(request.AnalyzeDurationMs * 1000).ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
analyzeDuration = EncodingUtils.GetAnalyzeDurationArgument(inputFiles.Length);
|
analyzeDuration = EncodingHelper.GetAnalyzeDurationArgument(inputFiles.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
probeSize = probeSize + " " + analyzeDuration;
|
probeSize = probeSize + " " + analyzeDuration;
|
||||||
|
@ -557,31 +557,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return EncodingUtils.GetInputArgument(inputFiles.ToList(), protocol);
|
return EncodingUtils.GetInputArgument(inputFiles.ToList(), protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the probe size argument.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="inputFiles">The input files.</param>
|
|
||||||
/// <param name="protocol">The protocol.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
public string GetProbeSizeAndAnalyzeDurationArgument(string[] inputFiles, MediaProtocol protocol)
|
|
||||||
{
|
|
||||||
var results = new List<string>();
|
|
||||||
|
|
||||||
var probeSize = EncodingUtils.GetProbeSizeArgument(inputFiles.Length);
|
|
||||||
var analyzeDuration = EncodingUtils.GetAnalyzeDurationArgument(inputFiles.Length);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(probeSize))
|
|
||||||
{
|
|
||||||
results.Add(probeSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(analyzeDuration))
|
|
||||||
{
|
|
||||||
results.Add(analyzeDuration);
|
|
||||||
}
|
|
||||||
return string.Join(" ", results.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the media info internal.
|
/// Gets the media info internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -984,11 +959,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
|
var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
|
||||||
string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
|
string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
|
||||||
|
|
||||||
var probeSize = GetProbeSizeAndAnalyzeDurationArgument(new[] { inputPath }, protocol);
|
var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
|
||||||
|
var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(probeSize))
|
if (!string.IsNullOrWhiteSpace(probeSizeArgument))
|
||||||
{
|
{
|
||||||
args = probeSize + " " + args;
|
args = probeSizeArgument + " " + args;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(analyzeDurationArgument))
|
||||||
|
{
|
||||||
|
args = analyzeDurationArgument + " " + args;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offset.HasValue)
|
if (offset.HasValue)
|
||||||
|
@ -1092,11 +1073,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
|
var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
|
||||||
|
|
||||||
var probeSize = GetProbeSizeAndAnalyzeDurationArgument(new[] { inputArgument }, protocol);
|
var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
|
||||||
|
var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(probeSize))
|
if (!string.IsNullOrWhiteSpace(probeSizeArgument))
|
||||||
{
|
{
|
||||||
args = probeSize + " " + args;
|
args = probeSizeArgument + " " + args;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(analyzeDurationArgument))
|
||||||
|
{
|
||||||
|
args = analyzeDurationArgument + " " + args;
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var process = _processFactory.Create(new ProcessOptions
|
||||||
|
|
|
@ -558,13 +558,36 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
? MediaStreamType.EmbeddedImage
|
? MediaStreamType.EmbeddedImage
|
||||||
: MediaStreamType.Video;
|
: MediaStreamType.Video;
|
||||||
|
|
||||||
|
stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
|
||||||
|
stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
|
||||||
|
|
||||||
|
if (isAudio || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
stream.Type = MediaStreamType.EmbeddedImage;
|
||||||
|
}
|
||||||
|
else if (string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// How to differentiate between video and embedded image?
|
||||||
|
// The only difference I've seen thus far is presence of codec tag, also embedded images have high (unusual) framerates
|
||||||
|
if (!string.IsNullOrWhiteSpace(stream.CodecTag))
|
||||||
|
{
|
||||||
|
stream.Type = MediaStreamType.Video;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.Type = MediaStreamType.EmbeddedImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.Type = MediaStreamType.Video;
|
||||||
|
}
|
||||||
|
|
||||||
stream.Width = streamInfo.width;
|
stream.Width = streamInfo.width;
|
||||||
stream.Height = streamInfo.height;
|
stream.Height = streamInfo.height;
|
||||||
stream.AspectRatio = GetAspectRatio(streamInfo);
|
stream.AspectRatio = GetAspectRatio(streamInfo);
|
||||||
|
|
||||||
stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
|
|
||||||
stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
|
|
||||||
|
|
||||||
if (streamInfo.bits_per_sample > 0)
|
if (streamInfo.bits_per_sample > 0)
|
||||||
{
|
{
|
||||||
stream.BitDepth = streamInfo.bits_per_sample;
|
stream.BitDepth = streamInfo.bits_per_sample;
|
||||||
|
|
|
@ -734,6 +734,16 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var charsetFromLanguage = string.IsNullOrWhiteSpace(language)
|
||||||
|
? null
|
||||||
|
: GetSubtitleFileCharacterSetFromLanguage(language);
|
||||||
|
|
||||||
|
// This assumption should only be made for external subtitles
|
||||||
|
if (!string.IsNullOrWhiteSpace(charsetFromLanguage) && !string.Equals(charsetFromLanguage, "windows-1252", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return charsetFromLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false);
|
var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(charset))
|
if (!string.IsNullOrWhiteSpace(charset))
|
||||||
|
@ -746,12 +756,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
return charset;
|
return charset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(language))
|
return charsetFromLanguage;
|
||||||
{
|
|
||||||
return GetSubtitleFileCharacterSetFromLanguage(language);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetSubtitleFileCharacterSetFromLanguage(string language)
|
public string GetSubtitleFileCharacterSetFromLanguage(string language)
|
||||||
|
|
|
@ -827,72 +827,6 @@ namespace MediaBrowser.Model.Dto
|
||||||
get { return StringHelper.EqualsIgnoreCase(MediaType, Entities.MediaType.Video); }
|
get { return StringHelper.EqualsIgnoreCase(MediaType, Entities.MediaType.Video); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this instance is audio.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is audio; otherwise, <c>false</c>.</value>
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsAudio
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(MediaType, Entities.MediaType.Audio); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this instance is game.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is game; otherwise, <c>false</c>.</value>
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsGame
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(MediaType, Entities.MediaType.Game); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this instance is person.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is person; otherwise, <c>false</c>.</value>
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsPerson
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(Type, "Person"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsMusicGenre
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(Type, "MusicGenre"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsGameGenre
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(Type, "GameGenre"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsGenre
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(Type, "Genre"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsArtist
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(Type, "MusicArtist"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsAlbum
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(Type, "MusicAlbum"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public bool IsStudio
|
|
||||||
{
|
|
||||||
get { return StringHelper.EqualsIgnoreCase(Type, "Studio"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the program identifier.
|
/// Gets or sets the program identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -60,6 +60,8 @@ namespace MediaBrowser.Model.Dto
|
||||||
public string TranscodingSubProtocol { get; set; }
|
public string TranscodingSubProtocol { get; set; }
|
||||||
public string TranscodingContainer { get; set; }
|
public string TranscodingContainer { get; set; }
|
||||||
|
|
||||||
|
public int? AnalyzeDurationMs { get; set; }
|
||||||
|
|
||||||
public MediaSourceInfo()
|
public MediaSourceInfo()
|
||||||
{
|
{
|
||||||
Formats = new List<string>();
|
Formats = new List<string>();
|
||||||
|
|
|
@ -90,6 +90,8 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// <value><c>true</c> if this instance has configured easy password; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance has configured easy password; otherwise, <c>false</c>.</value>
|
||||||
public bool HasConfiguredEasyPassword { get; set; }
|
public bool HasConfiguredEasyPassword { get; set; }
|
||||||
|
|
||||||
|
public bool? EnableAutoLogin { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the last login date.
|
/// Gets or sets the last login date.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
MediaLocationsCreated = new string[] { };
|
MediaLocationsCreated = new string[] { };
|
||||||
RecordingEncodingFormat = "mp4";
|
RecordingEncodingFormat = "mp4";
|
||||||
RecordingPostProcessorArguments = "\"{path}\"";
|
RecordingPostProcessorArguments = "\"{path}\"";
|
||||||
|
EnableRecordingEncoding = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,9 +137,9 @@
|
||||||
<Compile Include="Dto\NameValuePair.cs" />
|
<Compile Include="Dto\NameValuePair.cs" />
|
||||||
<Compile Include="Logging\IConsoleLogger.cs" />
|
<Compile Include="Logging\IConsoleLogger.cs" />
|
||||||
<Compile Include="Net\IpEndPointInfo.cs" />
|
<Compile Include="Net\IpEndPointInfo.cs" />
|
||||||
<Compile Include="Net\ISocket.cs" />
|
<Compile Include="Net\IAcceptSocket.cs" />
|
||||||
<Compile Include="Net\ISocketFactory.cs" />
|
<Compile Include="Net\ISocketFactory.cs" />
|
||||||
<Compile Include="Net\IUdpSocket.cs" />
|
<Compile Include="Net\ISocket.cs" />
|
||||||
<Compile Include="Net\SocketReceiveResult.cs" />
|
<Compile Include="Net\SocketReceiveResult.cs" />
|
||||||
<Compile Include="Services\IHttpResult.cs" />
|
<Compile Include="Services\IHttpResult.cs" />
|
||||||
<Compile Include="Social\ISharingRepository.cs" />
|
<Compile Include="Social\ISharingRepository.cs" />
|
||||||
|
|
28
MediaBrowser.Model/Net/IAcceptSocket.cs
Normal file
28
MediaBrowser.Model/Net/IAcceptSocket.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Model.Net
|
||||||
|
{
|
||||||
|
public interface IAcceptSocket : IDisposable
|
||||||
|
{
|
||||||
|
bool DualMode { get; }
|
||||||
|
IpEndPointInfo LocalEndPoint { get; }
|
||||||
|
IpEndPointInfo RemoteEndPoint { get; }
|
||||||
|
void Close();
|
||||||
|
void Shutdown(bool both);
|
||||||
|
void Listen(int backlog);
|
||||||
|
void Bind(IpEndPointInfo endpoint);
|
||||||
|
void Connect(IpEndPointInfo endPoint);
|
||||||
|
void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SocketCreateException : Exception
|
||||||
|
{
|
||||||
|
public SocketCreateException(string errorCode, Exception originalException)
|
||||||
|
: base(errorCode, originalException)
|
||||||
|
{
|
||||||
|
ErrorCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ErrorCode { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +1,28 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Net
|
namespace MediaBrowser.Model.Net
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a common interface across platforms for UDP sockets used by this SSDP implementation.
|
||||||
|
/// </summary>
|
||||||
public interface ISocket : IDisposable
|
public interface ISocket : IDisposable
|
||||||
{
|
{
|
||||||
bool DualMode { get; }
|
IpAddressInfo LocalIPAddress { get; }
|
||||||
IpEndPointInfo LocalEndPoint { get; }
|
|
||||||
IpEndPointInfo RemoteEndPoint { get; }
|
|
||||||
void Close();
|
|
||||||
void Shutdown(bool both);
|
|
||||||
void Listen(int backlog);
|
|
||||||
void Bind(IpEndPointInfo endpoint);
|
|
||||||
|
|
||||||
void StartAccept(Action<ISocket> onAccept, Func<bool> isClosed);
|
/// <summary>
|
||||||
}
|
/// Waits for and returns the next UDP message sent to this socket (uni or multicast).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<SocketReceiveResult> ReceiveAsync(CancellationToken cancellationToken);
|
||||||
|
|
||||||
public class SocketCreateException : Exception
|
/// <summary>
|
||||||
{
|
/// Sends a UDP message to a particular end point (uni or multicast).
|
||||||
public SocketCreateException(string errorCode, Exception originalException)
|
/// </summary>
|
||||||
: base(errorCode, originalException)
|
Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken);
|
||||||
{
|
|
||||||
ErrorCode = errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ErrorCode { get; private set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
namespace MediaBrowser.Model.Net
|
namespace MediaBrowser.Model.Net
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implemented by components that can create a platform specific UDP socket implementation, and wrap it in the cross platform <see cref="IUdpSocket"/> interface.
|
/// Implemented by components that can create a platform specific UDP socket implementation, and wrap it in the cross platform <see cref="ISocket"/> interface.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISocketFactory
|
public interface ISocketFactory
|
||||||
{
|
{
|
||||||
|
@ -11,13 +11,15 @@ namespace MediaBrowser.Model.Net
|
||||||
/// Createa a new unicast socket using the specified local port number.
|
/// Createa a new unicast socket using the specified local port number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="localPort">The local port to bind to.</param>
|
/// <param name="localPort">The local port to bind to.</param>
|
||||||
/// <returns>A <see cref="IUdpSocket"/> implementation.</returns>
|
/// <returns>A <see cref="ISocket"/> implementation.</returns>
|
||||||
IUdpSocket CreateUdpSocket(int localPort);
|
ISocket CreateUdpSocket(int localPort);
|
||||||
|
|
||||||
|
ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Createa a new unicast socket using the specified local port number.
|
/// Createa a new unicast socket using the specified local port number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIp, int localPort);
|
ISocket CreateSsdpUdpSocket(IpAddressInfo localIp, int localPort);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port.
|
/// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port.
|
||||||
|
@ -25,10 +27,10 @@ namespace MediaBrowser.Model.Net
|
||||||
/// <param name="ipAddress">The multicast IP address to bind to.</param>
|
/// <param name="ipAddress">The multicast IP address to bind to.</param>
|
||||||
/// <param name="multicastTimeToLive">The multicast time to live value. Actually a maximum number of network hops for UDP packets.</param>
|
/// <param name="multicastTimeToLive">The multicast time to live value. Actually a maximum number of network hops for UDP packets.</param>
|
||||||
/// <param name="localPort">The local port to bind to.</param>
|
/// <param name="localPort">The local port to bind to.</param>
|
||||||
/// <returns>A <see cref="IUdpSocket"/> implementation.</returns>
|
/// <returns>A <see cref="ISocket"/> implementation.</returns>
|
||||||
IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort);
|
ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort);
|
||||||
|
|
||||||
ISocket CreateSocket(IpAddressFamily family, SocketType socketType, ProtocolType protocolType, bool dualMode);
|
IAcceptSocket CreateSocket(IpAddressFamily family, SocketType socketType, ProtocolType protocolType, bool dualMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SocketType
|
public enum SocketType
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Net
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a common interface across platforms for UDP sockets used by this SSDP implementation.
|
|
||||||
/// </summary>
|
|
||||||
public interface IUdpSocket : IDisposable
|
|
||||||
{
|
|
||||||
IpAddressInfo LocalIPAddress { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Waits for and returns the next UDP message sent to this socket (uni or multicast).
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task<SocketReceiveResult> ReceiveAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends a UDP message to a particular end point (uni or multicast).
|
|
||||||
/// </summary>
|
|
||||||
Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -322,6 +322,9 @@ namespace MediaBrowser.Model.Net
|
||||||
throw new ArgumentNullException("mimeType");
|
throw new ArgumentNullException("mimeType");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle text/html; charset=UTF-8
|
||||||
|
mimeType = mimeType.Split(';')[0];
|
||||||
|
|
||||||
string result;
|
string result;
|
||||||
if (ExtensionLookup.TryGetValue(mimeType, out result))
|
if (ExtensionLookup.TryGetValue(mimeType, out result))
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,11 +20,6 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
AwardSummary,
|
AwardSummary,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The budget
|
|
||||||
/// </summary>
|
|
||||||
Budget,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The can delete
|
/// The can delete
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -156,6 +151,8 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
People,
|
People,
|
||||||
|
|
||||||
|
PlayAccess,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The production locations
|
/// The production locations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -173,11 +170,6 @@
|
||||||
|
|
||||||
RecursiveItemCount,
|
RecursiveItemCount,
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The revenue
|
|
||||||
/// </summary>
|
|
||||||
Revenue,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The season name
|
/// The season name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -20,14 +20,6 @@ namespace MediaBrowser.Model.Querying
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string Artist = "Artist";
|
public const string Artist = "Artist";
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The budget
|
|
||||||
/// </summary>
|
|
||||||
public const string Budget = "Budget";
|
|
||||||
/// <summary>
|
|
||||||
/// The revenue
|
|
||||||
/// </summary>
|
|
||||||
public const string Revenue = "Revenue";
|
|
||||||
/// <summary>
|
|
||||||
/// The date created
|
/// The date created
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DateCreated = "DateCreated";
|
public const string DateCreated = "DateCreated";
|
||||||
|
|
|
@ -73,6 +73,10 @@ namespace MediaBrowser.Model.Session
|
||||||
/// <value>The volume level.</value>
|
/// <value>The volume level.</value>
|
||||||
public int? VolumeLevel { get; set; }
|
public int? VolumeLevel { get; set; }
|
||||||
|
|
||||||
|
public int? Brightness { get; set; }
|
||||||
|
|
||||||
|
public string AspectRatio { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the play method.
|
/// Gets or sets the play method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -410,6 +410,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
var folderName = item is MusicAlbum ||
|
var folderName = item is MusicAlbum ||
|
||||||
item is MusicArtist ||
|
item is MusicArtist ||
|
||||||
item is PhotoAlbum ||
|
item is PhotoAlbum ||
|
||||||
|
item is Person ||
|
||||||
(saveLocally && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy) ?
|
(saveLocally && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy) ?
|
||||||
"folder" :
|
"folder" :
|
||||||
"poster";
|
"poster";
|
||||||
|
|
|
@ -195,7 +195,6 @@ namespace MediaBrowser.Providers.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
MergeAlbumArtist(source, target, lockedFields, replaceData);
|
MergeAlbumArtist(source, target, lockedFields, replaceData);
|
||||||
MergeBudget(source, target, lockedFields, replaceData);
|
|
||||||
MergeMetascore(source, target, lockedFields, replaceData);
|
MergeMetascore(source, target, lockedFields, replaceData);
|
||||||
MergeCriticRating(source, target, lockedFields, replaceData);
|
MergeCriticRating(source, target, lockedFields, replaceData);
|
||||||
MergeAwards(source, target, lockedFields, replaceData);
|
MergeAwards(source, target, lockedFields, replaceData);
|
||||||
|
@ -247,25 +246,6 @@ namespace MediaBrowser.Providers.Manager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MergeBudget(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
|
||||||
{
|
|
||||||
var sourceHasBudget = source as IHasBudget;
|
|
||||||
var targetHasBudget = target as IHasBudget;
|
|
||||||
|
|
||||||
if (sourceHasBudget != null && targetHasBudget != null)
|
|
||||||
{
|
|
||||||
if (replaceData || !targetHasBudget.Budget.HasValue)
|
|
||||||
{
|
|
||||||
targetHasBudget.Budget = sourceHasBudget.Budget;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replaceData || !targetHasBudget.Revenue.HasValue)
|
|
||||||
{
|
|
||||||
targetHasBudget.Revenue = sourceHasBudget.Revenue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void MergeMetascore(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
private static void MergeMetascore(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
||||||
{
|
{
|
||||||
var sourceCast = source as IHasMetascore;
|
var sourceCast = source as IHasMetascore;
|
||||||
|
|
|
@ -136,13 +136,6 @@ namespace MediaBrowser.Providers.Movies
|
||||||
|
|
||||||
movie.HomePageUrl = movieData.homepage;
|
movie.HomePageUrl = movieData.homepage;
|
||||||
|
|
||||||
var hasBudget = movie as IHasBudget;
|
|
||||||
if (hasBudget != null)
|
|
||||||
{
|
|
||||||
hasBudget.Budget = movieData.budget;
|
|
||||||
hasBudget.Revenue = movieData.revenue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(movieData.tagline))
|
if (!string.IsNullOrEmpty(movieData.tagline))
|
||||||
{
|
{
|
||||||
movie.Tagline = movieData.tagline;
|
movie.Tagline = movieData.tagline;
|
||||||
|
|
|
@ -170,11 +170,6 @@ namespace MediaBrowser.Providers.TV
|
||||||
/// <exception cref="System.ArgumentNullException">seriesId</exception>
|
/// <exception cref="System.ArgumentNullException">seriesId</exception>
|
||||||
internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
|
internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(seriesId))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("seriesId");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DownloadSeriesZip(seriesId, idType, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
|
await DownloadSeriesZip(seriesId, idType, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
|
@ -329,9 +329,6 @@
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\favicon.ico">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\favicon.ico">
|
||||||
<Link>Resources\dashboard-ui\favicon.ico</Link>
|
<Link>Resources\dashboard-ui\favicon.ico</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\favorites.html">
|
|
||||||
<Link>Resources\dashboard-ui\favorites.html</Link>
|
|
||||||
</BundleResource>
|
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\forgotpassword.html">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\forgotpassword.html">
|
||||||
<Link>Resources\dashboard-ui\forgotpassword.html</Link>
|
<Link>Resources\dashboard-ui\forgotpassword.html</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
@ -665,9 +662,6 @@
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\localassetmanager.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\localassetmanager.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\localassetmanager.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\localassetmanager.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\nullassetmanager.js">
|
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\nullassetmanager.js</Link>
|
|
||||||
</BundleResource>
|
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\serverdiscovery-chrome.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\serverdiscovery-chrome.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\serverdiscovery-chrome.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\serverdiscovery-chrome.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
@ -698,9 +692,6 @@
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\multiserversync.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\multiserversync.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\multiserversync.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\multiserversync.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\offlineusersync.js">
|
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\offlineusersync.js</Link>
|
|
||||||
</BundleResource>
|
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\serversync.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\serversync.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\serversync.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\serversync.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
@ -710,9 +701,6 @@
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\useractionrepository.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\useractionrepository.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\useractionrepository.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\useractionrepository.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\sync\userrepository.js">
|
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\sync\userrepository.js</Link>
|
|
||||||
</BundleResource>
|
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\appsettings.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\appsettings.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\appsettings.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\appsettings.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
@ -1205,6 +1193,9 @@
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\autoplaydetect.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\autoplaydetect.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\autoplaydetect.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\autoplaydetect.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\brightnessosd.js">
|
||||||
|
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\brightnessosd.js</Link>
|
||||||
|
</BundleResource>
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\iconosd.css">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\iconosd.css">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\iconosd.css</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\iconosd.css</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
@ -1214,6 +1205,9 @@
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\nowplayinghelper.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\nowplayinghelper.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\nowplayinghelper.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\nowplayinghelper.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\playaccessvalidation.js">
|
||||||
|
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\playaccessvalidation.js</Link>
|
||||||
|
</BundleResource>
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\playbackmanager.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playback\playbackmanager.js">
|
||||||
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\playbackmanager.js</Link>
|
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playback\playbackmanager.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
@ -1886,6 +1880,9 @@
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\site.css">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\site.css">
|
||||||
<Link>Resources\dashboard-ui\css\site.css</Link>
|
<Link>Resources\dashboard-ui\css\site.css</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\videoosd.css">
|
||||||
|
<Link>Resources\dashboard-ui\css\videoosd.css</Link>
|
||||||
|
</BundleResource>
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\ani_equalizer_black.gif">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\ani_equalizer_black.gif">
|
||||||
<Link>Resources\dashboard-ui\css\images\ani_equalizer_black.gif</Link>
|
<Link>Resources\dashboard-ui\css\images\ani_equalizer_black.gif</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
@ -2228,9 +2225,6 @@
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\episodes.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\episodes.js">
|
||||||
<Link>Resources\dashboard-ui\scripts\episodes.js</Link>
|
<Link>Resources\dashboard-ui\scripts\episodes.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\favorites.js">
|
|
||||||
<Link>Resources\dashboard-ui\scripts\favorites.js</Link>
|
|
||||||
</BundleResource>
|
|
||||||
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\forgotpassword.js">
|
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\forgotpassword.js">
|
||||||
<Link>Resources\dashboard-ui\scripts\forgotpassword.js</Link>
|
<Link>Resources\dashboard-ui\scripts\forgotpassword.js</Link>
|
||||||
</BundleResource>
|
</BundleResource>
|
||||||
|
|
|
@ -61,6 +61,9 @@
|
||||||
<Reference Include="Emby.Common.Implementations">
|
<Reference Include="Emby.Common.Implementations">
|
||||||
<HintPath>..\ThirdParty\emby\Emby.Common.Implementations.dll</HintPath>
|
<HintPath>..\ThirdParty\emby\Emby.Common.Implementations.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Emby.Server.CinemaMode">
|
||||||
|
<HintPath>..\ThirdParty\emby\Emby.Server.CinemaMode.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Emby.Server.Connect">
|
<Reference Include="Emby.Server.Connect">
|
||||||
<HintPath>..\ThirdParty\emby\Emby.Server.Connect.dll</HintPath>
|
<HintPath>..\ThirdParty\emby\Emby.Server.Connect.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Emby.Server.CinemaMode;
|
||||||
using Emby.Server.Connect;
|
using Emby.Server.Connect;
|
||||||
using Emby.Server.Core;
|
using Emby.Server.Core;
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
|
@ -58,6 +59,7 @@ namespace MediaBrowser.Server.Mono
|
||||||
{
|
{
|
||||||
var list = new List<Assembly>();
|
var list = new List<Assembly>();
|
||||||
|
|
||||||
|
list.Add(typeof(DefaultIntroProvider).Assembly);
|
||||||
list.Add(typeof(LinuxIsoManager).Assembly);
|
list.Add(typeof(LinuxIsoManager).Assembly);
|
||||||
list.Add(typeof(ConnectManager).Assembly);
|
list.Add(typeof(ConnectManager).Assembly);
|
||||||
list.Add(typeof(SyncManager).Assembly);
|
list.Add(typeof(SyncManager).Assembly);
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using IniParser;
|
|
||||||
using IniParser.Model;
|
|
||||||
using MediaBrowser.Controller.LiveTv;
|
|
||||||
using MediaBrowser.Model.LiveTv;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
|
|
||||||
{
|
|
||||||
public class ChannelScan
|
|
||||||
{
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public ChannelScan(ILogger logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<ChannelInfo>> Scan(TunerHostInfo info, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var ini = info.SourceA.Split('|')[1];
|
|
||||||
var resource = GetType().Assembly.GetManifestResourceNames().FirstOrDefault(i => i.EndsWith(ini, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
_logger.Info("Opening ini file {0}", resource);
|
|
||||||
var list = new List<ChannelInfo>();
|
|
||||||
|
|
||||||
using (var stream = GetType().Assembly.GetManifestResourceStream(resource))
|
|
||||||
{
|
|
||||||
using (var reader = new StreamReader(stream))
|
|
||||||
{
|
|
||||||
var parser = new StreamIniDataParser();
|
|
||||||
var data = parser.ReadData(reader);
|
|
||||||
|
|
||||||
var count = GetInt(data, "DVB", "0", 0);
|
|
||||||
|
|
||||||
_logger.Info("DVB Count: {0}", count);
|
|
||||||
|
|
||||||
var index = 1;
|
|
||||||
var source = "1";
|
|
||||||
|
|
||||||
while (index <= count)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
using (var rtspSession = new RtspSession(info.Url, _logger))
|
|
||||||
{
|
|
||||||
float percent = count == 0 ? 0 : (float)(index) / count;
|
|
||||||
percent = Math.Max(percent * 100, 100);
|
|
||||||
|
|
||||||
//SetControlPropertyThreadSafe(pgbSearchResult, "Value", (int)percent);
|
|
||||||
var strArray = data["DVB"][index.ToString(CultureInfo.InvariantCulture)].Split(',');
|
|
||||||
|
|
||||||
string tuning;
|
|
||||||
if (strArray[4] == "S2")
|
|
||||||
{
|
|
||||||
tuning = string.Format("src={0}&freq={1}&pol={2}&sr={3}&fec={4}&msys=dvbs2&mtype={5}&plts=on&ro=0.35&pids=0,16,17,18,20", source, strArray[0], strArray[1].ToLower(), strArray[2].ToLower(), strArray[3], strArray[5].ToLower());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tuning = string.Format("src={0}&freq={1}&pol={2}&sr={3}&fec={4}&msys=dvbs&mtype={5}&pids=0,16,17,18,20", source, strArray[0], strArray[1].ToLower(), strArray[2], strArray[3], strArray[5].ToLower());
|
|
||||||
}
|
|
||||||
|
|
||||||
rtspSession.Setup(tuning, "unicast");
|
|
||||||
|
|
||||||
rtspSession.Play(string.Empty);
|
|
||||||
|
|
||||||
int signallevel;
|
|
||||||
int signalQuality;
|
|
||||||
rtspSession.Describe(out signallevel, out signalQuality);
|
|
||||||
|
|
||||||
await Task.Delay(500).ConfigureAwait(false);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GetInt(IniData data, string s1, string s2, int defaultValue)
|
|
||||||
{
|
|
||||||
var value = data[s1][s2];
|
|
||||||
int numericValue;
|
|
||||||
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out numericValue))
|
|
||||||
{
|
|
||||||
return numericValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SatChannel
|
|
||||||
{
|
|
||||||
// TODO: Add properties
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) <2007-2016> <Kay Diefenthal>
|
|
||||||
|
|
||||||
SatIp.RtspSample is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SatIp.RtspSample is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
|
|
||||||
{
|
|
||||||
public class ReportBlock
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get the length of the block.
|
|
||||||
/// </summary>
|
|
||||||
public int BlockLength { get { return (24); } }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the synchronization source.
|
|
||||||
/// </summary>
|
|
||||||
public string SynchronizationSource { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the fraction lost.
|
|
||||||
/// </summary>
|
|
||||||
public int FractionLost { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the cumulative packets lost.
|
|
||||||
/// </summary>
|
|
||||||
public int CumulativePacketsLost { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the highest number received.
|
|
||||||
/// </summary>
|
|
||||||
public int HighestNumberReceived { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the inter arrival jitter.
|
|
||||||
/// </summary>
|
|
||||||
public int InterArrivalJitter { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the timestamp of the last report.
|
|
||||||
/// </summary>
|
|
||||||
public int LastReportTimeStamp { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the delay since the last report.
|
|
||||||
/// </summary>
|
|
||||||
public int DelaySinceLastReport { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize a new instance of the ReportBlock class.
|
|
||||||
/// </summary>
|
|
||||||
public ReportBlock() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unpack the data in a packet.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buffer">The buffer containing the packet.</param>
|
|
||||||
/// <param name="offset">The offset to the first byte of the packet within the buffer.</param>
|
|
||||||
/// <returns>An ErrorSpec instance if an error occurs; null otherwise.</returns>
|
|
||||||
public void Process(byte[] buffer, int offset)
|
|
||||||
{
|
|
||||||
SynchronizationSource = Utils.ConvertBytesToString(buffer, offset, 4);
|
|
||||||
FractionLost = buffer[offset + 4];
|
|
||||||
CumulativePacketsLost = Utils.Convert3BytesToInt(buffer, offset + 5);
|
|
||||||
HighestNumberReceived = Utils.Convert4BytesToInt(buffer, offset + 8);
|
|
||||||
InterArrivalJitter = Utils.Convert4BytesToInt(buffer, offset + 12);
|
|
||||||
LastReportTimeStamp = Utils.Convert4BytesToInt(buffer, offset + 16);
|
|
||||||
DelaySinceLastReport = Utils.Convert4BytesToInt(buffer, offset + 20);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) <2007-2016> <Kay Diefenthal>
|
|
||||||
|
|
||||||
SatIp.RtspSample is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SatIp.RtspSample is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
|
|
||||||
{
|
|
||||||
class RtcpAppPacket : RtcpPacket
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get the synchronization source.
|
|
||||||
/// </summary>
|
|
||||||
public int SynchronizationSource { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the name.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the identity.
|
|
||||||
/// </summary>
|
|
||||||
public int Identity { get; private set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Get the variable data portion.
|
|
||||||
/// </summary>
|
|
||||||
public string Data { get; private set; }
|
|
||||||
|
|
||||||
public override void Parse(byte[] buffer, int offset)
|
|
||||||
{
|
|
||||||
base.Parse(buffer, offset);
|
|
||||||
SynchronizationSource = Utils.Convert4BytesToInt(buffer, offset + 4);
|
|
||||||
Name = Utils.ConvertBytesToString(buffer, offset + 8, 4);
|
|
||||||
Identity = Utils.Convert2BytesToInt(buffer, offset + 12);
|
|
||||||
|
|
||||||
int dataLength = Utils.Convert2BytesToInt(buffer, offset + 14);
|
|
||||||
if (dataLength != 0)
|
|
||||||
Data = Utils.ConvertBytesToString(buffer, offset + 16, dataLength);
|
|
||||||
}
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.AppendFormat("Application Specific.\n");
|
|
||||||
sb.AppendFormat("Version : {0} .\n", Version);
|
|
||||||
sb.AppendFormat("Padding : {0} .\n", Padding);
|
|
||||||
sb.AppendFormat("Report Count : {0} .\n", ReportCount);
|
|
||||||
sb.AppendFormat("PacketType: {0} .\n", Type);
|
|
||||||
sb.AppendFormat("Length : {0} .\n", Length);
|
|
||||||
sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource);
|
|
||||||
sb.AppendFormat("Name : {0} .\n", Name);
|
|
||||||
sb.AppendFormat("Identity : {0} .\n", Identity);
|
|
||||||
sb.AppendFormat("Data : {0} .\n", Data);
|
|
||||||
sb.AppendFormat(".\n");
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
/*
|
|
||||||
Copyright (C) <2007-2016> <Kay Diefenthal>
|
|
||||||
|
|
||||||
SatIp.RtspSample is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SatIp.RtspSample is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
|
|
||||||
{
|
|
||||||
public class RtcpByePacket :RtcpPacket
|
|
||||||
{
|
|
||||||
public Collection<string> SynchronizationSources { get; private set; }
|
|
||||||
public string ReasonForLeaving { get; private set; }
|
|
||||||
public override void Parse(byte[] buffer, int offset)
|
|
||||||
{
|
|
||||||
base.Parse(buffer, offset);
|
|
||||||
SynchronizationSources = new Collection<string>();
|
|
||||||
int index = 4;
|
|
||||||
|
|
||||||
while (SynchronizationSources.Count < ReportCount)
|
|
||||||
{
|
|
||||||
SynchronizationSources.Add(Utils.ConvertBytesToString(buffer, offset + index, 4));
|
|
||||||
index += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < Length)
|
|
||||||
{
|
|
||||||
int reasonLength = buffer[offset + index];
|
|
||||||
ReasonForLeaving = Utils.ConvertBytesToString(buffer, offset + index + 1, reasonLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.AppendFormat("ByeBye .\n");
|
|
||||||
sb.AppendFormat("Version : {0} .\n", Version);
|
|
||||||
sb.AppendFormat("Padding : {0} .\n", Padding);
|
|
||||||
sb.AppendFormat("Report Count : {0} .\n", ReportCount);
|
|
||||||
sb.AppendFormat("PacketType: {0} .\n", Type);
|
|
||||||
sb.AppendFormat("Length : {0} .\n", Length);
|
|
||||||
sb.AppendFormat("SynchronizationSources : {0} .\n", SynchronizationSources);
|
|
||||||
sb.AppendFormat("ReasonForLeaving : {0} .\n", ReasonForLeaving);
|
|
||||||
sb.AppendFormat(".\n");
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) <2007-2016> <Kay Diefenthal>
|
|
||||||
|
|
||||||
SatIp.RtspSample is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SatIp.RtspSample is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
using System;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Threading;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
|
|
||||||
{
|
|
||||||
public class RtcpListener
|
|
||||||
{
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private Thread _rtcpListenerThread;
|
|
||||||
private AutoResetEvent _rtcpListenerThreadStopEvent = null;
|
|
||||||
private UdpClient _udpClient;
|
|
||||||
private IPEndPoint _multicastEndPoint;
|
|
||||||
private IPEndPoint _serverEndPoint;
|
|
||||||
private TransmissionMode _transmissionMode;
|
|
||||||
|
|
||||||
public RtcpListener(String address, int port, TransmissionMode mode,ILogger logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_transmissionMode = mode;
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case TransmissionMode.Unicast:
|
|
||||||
_udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(address), port));
|
|
||||||
_serverEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
|
||||||
break;
|
|
||||||
case TransmissionMode.Multicast:
|
|
||||||
_multicastEndPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
|
||||||
_serverEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
|
||||||
_udpClient = new UdpClient();
|
|
||||||
_udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
|
|
||||||
_udpClient.ExclusiveAddressUse = false;
|
|
||||||
_udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port));
|
|
||||||
_udpClient.JoinMulticastGroup(_multicastEndPoint.Address);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
//StartRtcpListenerThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartRtcpListenerThread()
|
|
||||||
{
|
|
||||||
// Kill the existing thread if it is in "zombie" state.
|
|
||||||
if (_rtcpListenerThread != null && !_rtcpListenerThread.IsAlive)
|
|
||||||
{
|
|
||||||
StopRtcpListenerThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_rtcpListenerThread == null)
|
|
||||||
{
|
|
||||||
_logger.Info("SAT>IP : starting new RTCP listener thread");
|
|
||||||
_rtcpListenerThreadStopEvent = new AutoResetEvent(false);
|
|
||||||
_rtcpListenerThread = new Thread(new ThreadStart(RtcpListenerThread));
|
|
||||||
_rtcpListenerThread.Name = string.Format("SAT>IP tuner RTCP listener");
|
|
||||||
_rtcpListenerThread.IsBackground = true;
|
|
||||||
_rtcpListenerThread.Priority = ThreadPriority.Lowest;
|
|
||||||
_rtcpListenerThread.Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopRtcpListenerThread()
|
|
||||||
{
|
|
||||||
if (_rtcpListenerThread != null)
|
|
||||||
{
|
|
||||||
if (!_rtcpListenerThread.IsAlive)
|
|
||||||
{
|
|
||||||
_logger.Info("SAT>IP : aborting old RTCP listener thread");
|
|
||||||
_rtcpListenerThread.Abort();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_rtcpListenerThreadStopEvent.Set();
|
|
||||||
if (!_rtcpListenerThread.Join(400 * 2))
|
|
||||||
{
|
|
||||||
_logger.Info("SAT>IP : failed to join RTCP listener thread, aborting thread");
|
|
||||||
_rtcpListenerThread.Abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_rtcpListenerThread = null;
|
|
||||||
if (_rtcpListenerThreadStopEvent != null)
|
|
||||||
{
|
|
||||||
_rtcpListenerThreadStopEvent.Close();
|
|
||||||
_rtcpListenerThreadStopEvent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RtcpListenerThread()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool receivedGoodBye = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_udpClient.Client.ReceiveTimeout = 400;
|
|
||||||
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
|
||||||
while (!receivedGoodBye && !_rtcpListenerThreadStopEvent.WaitOne(1))
|
|
||||||
{
|
|
||||||
byte[] packets = _udpClient.Receive(ref serverEndPoint);
|
|
||||||
if (packets == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
while (offset < packets.Length)
|
|
||||||
{
|
|
||||||
switch (packets[offset + 1])
|
|
||||||
{
|
|
||||||
case 200: //sr
|
|
||||||
var sr = new RtcpSenderReportPacket();
|
|
||||||
sr.Parse(packets, offset);
|
|
||||||
offset += sr.Length;
|
|
||||||
break;
|
|
||||||
case 201: //rr
|
|
||||||
var rr = new RtcpReceiverReportPacket();
|
|
||||||
rr.Parse(packets, offset);
|
|
||||||
offset += rr.Length;
|
|
||||||
break;
|
|
||||||
case 202: //sd
|
|
||||||
var sd = new RtcpSourceDescriptionPacket();
|
|
||||||
sd.Parse(packets, offset);
|
|
||||||
offset += sd.Length;
|
|
||||||
break;
|
|
||||||
case 203: // bye
|
|
||||||
var bye = new RtcpByePacket();
|
|
||||||
bye.Parse(packets, offset);
|
|
||||||
receivedGoodBye = true;
|
|
||||||
OnPacketReceived(new RtcpPacketReceivedArgs(bye));
|
|
||||||
offset += bye.Length;
|
|
||||||
break;
|
|
||||||
case 204: // app
|
|
||||||
var app = new RtcpAppPacket();
|
|
||||||
app.Parse(packets, offset);
|
|
||||||
OnPacketReceived(new RtcpPacketReceivedArgs(app));
|
|
||||||
offset += app.Length;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
switch (_transmissionMode)
|
|
||||||
{
|
|
||||||
case TransmissionMode.Multicast:
|
|
||||||
_udpClient.DropMulticastGroup(_multicastEndPoint.Address);
|
|
||||||
_udpClient.Close();
|
|
||||||
break;
|
|
||||||
case TransmissionMode.Unicast:
|
|
||||||
_udpClient.Close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ThreadAbortException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Info(string.Format("SAT>IP : RTCP listener thread exception"), ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_logger.Info("SAT>IP : RTCP listener thread stopping");
|
|
||||||
}
|
|
||||||
public delegate void PacketReceivedHandler(object sender, RtcpPacketReceivedArgs e);
|
|
||||||
public event PacketReceivedHandler PacketReceived;
|
|
||||||
public class RtcpPacketReceivedArgs : EventArgs
|
|
||||||
{
|
|
||||||
public Object Packet { get; private set; }
|
|
||||||
|
|
||||||
public RtcpPacketReceivedArgs(Object packet)
|
|
||||||
{
|
|
||||||
Packet = packet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected void OnPacketReceived(RtcpPacketReceivedArgs args)
|
|
||||||
{
|
|
||||||
if (PacketReceived != null)
|
|
||||||
{
|
|
||||||
PacketReceived(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) <2007-2016> <Kay Diefenthal>
|
|
||||||
|
|
||||||
SatIp.RtspSample is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SatIp.RtspSample is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
|
|
||||||
{
|
|
||||||
public abstract class RtcpPacket
|
|
||||||
{
|
|
||||||
public int Version { get; private set; }
|
|
||||||
public bool Padding { get; private set; }
|
|
||||||
public int ReportCount { get; private set; }
|
|
||||||
public int Type { get; private set; }
|
|
||||||
public int Length { get; private set; }
|
|
||||||
|
|
||||||
public virtual void Parse(byte[] buffer, int offset)
|
|
||||||
{
|
|
||||||
Version = buffer[offset] >> 6;
|
|
||||||
Padding = (buffer[offset] & 0x20) != 0;
|
|
||||||
ReportCount = buffer[offset] & 0x1f;
|
|
||||||
Type = buffer[offset + 1];
|
|
||||||
Length = (Utils.Convert2BytesToInt(buffer, offset + 2) * 4) + 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
/*
|
|
||||||
Copyright (C) <2007-2016> <Kay Diefenthal>
|
|
||||||
|
|
||||||
SatIp.RtspSample is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SatIp.RtspSample is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
|
|
||||||
{
|
|
||||||
public class RtcpReceiverReportPacket :RtcpPacket
|
|
||||||
{
|
|
||||||
public string SynchronizationSource { get; private set; }
|
|
||||||
public Collection<ReportBlock> ReportBlocks { get; private set; }
|
|
||||||
public byte[] ProfileExtension { get; private set; }
|
|
||||||
public override void Parse(byte[] buffer, int offset)
|
|
||||||
{
|
|
||||||
base.Parse(buffer, offset);
|
|
||||||
SynchronizationSource = Utils.ConvertBytesToString(buffer, offset + 4, 4);
|
|
||||||
|
|
||||||
ReportBlocks = new Collection<ReportBlock>();
|
|
||||||
int index = 8;
|
|
||||||
|
|
||||||
while (ReportBlocks.Count < ReportCount)
|
|
||||||
{
|
|
||||||
ReportBlock reportBlock = new ReportBlock();
|
|
||||||
reportBlock.Process(buffer, offset + index);
|
|
||||||
ReportBlocks.Add(reportBlock);
|
|
||||||
index += reportBlock.BlockLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < Length)
|
|
||||||
{
|
|
||||||
ProfileExtension = new byte[Length - index];
|
|
||||||
|
|
||||||
for (int extensionIndex = 0; index < Length; index++)
|
|
||||||
{
|
|
||||||
ProfileExtension[extensionIndex] = buffer[offset + index];
|
|
||||||
extensionIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.AppendFormat("Receiver Report.\n");
|
|
||||||
sb.AppendFormat("Version : {0} .\n", Version);
|
|
||||||
sb.AppendFormat("Padding : {0} .\n", Padding);
|
|
||||||
sb.AppendFormat("Report Count : {0} .\n", ReportCount);
|
|
||||||
sb.AppendFormat("PacketType: {0} .\n", Type);
|
|
||||||
sb.AppendFormat("Length : {0} .\n", Length);
|
|
||||||
sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource);
|
|
||||||
sb.AppendFormat(".\n");
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user