This commit is contained in:
Luke Pulverenti 2017-03-08 02:24:52 -05:00
commit d5c7845917
295 changed files with 1430 additions and 13702 deletions

View File

@ -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);

View File

@ -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

View File

@ -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
} }
} }

View File

@ -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();

View File

@ -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>

View File

@ -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,
} }
}; };

View File

@ -113,6 +113,15 @@ namespace Emby.Dlna.Profiles
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
ResponseProfiles = new ResponseProfile[] { }; ResponseProfiles = new ResponseProfile[] { };
} }
} }

View File

@ -214,6 +214,14 @@ namespace Emby.Dlna.Profiles
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -40,6 +40,15 @@ namespace Emby.Dlna.Profiles
MimeType = "video/mp4" MimeType = "video/mp4"
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -210,6 +210,15 @@ namespace Emby.Dlna.Profiles
MimeType = "video/mp4" MimeType = "video/mp4"
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -269,6 +269,15 @@ namespace Emby.Dlna.Profiles
Type = DlnaProfileType.Audio Type = DlnaProfileType.Audio
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -351,6 +351,15 @@ namespace Emby.Dlna.Profiles
} }
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -374,6 +374,15 @@ namespace Emby.Dlna.Profiles
} }
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -292,6 +292,15 @@ namespace Emby.Dlna.Profiles
} }
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -310,6 +310,15 @@ namespace Emby.Dlna.Profiles
} }
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -310,6 +310,15 @@ namespace Emby.Dlna.Profiles
} }
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -255,6 +255,15 @@ namespace Emby.Dlna.Profiles
Type = DlnaProfileType.Audio Type = DlnaProfileType.Audio
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -264,6 +264,15 @@ namespace Emby.Dlna.Profiles
MimeType = "video/mp4" MimeType = "video/mp4"
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -125,7 +125,7 @@ namespace Emby.Dlna.Profiles
new DirectPlayProfile new DirectPlayProfile
{ {
Container = "flac,ac3", Container = "flac",
Type = DlnaProfileType.Audio Type = DlnaProfileType.Audio
}, },

View File

@ -312,6 +312,15 @@ namespace Emby.Dlna.Profiles
} }
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -357,6 +357,15 @@ namespace Emby.Dlna.Profiles
MimeType = "video/mp4" MimeType = "video/mp4"
} }
}; };
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
} }
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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",

View File

@ -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);
} }

View File

@ -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)

View File

@ -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);

View File

@ -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));

View File

@ -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);
} }

View File

@ -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" />

View File

@ -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);

View File

@ -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"
}
};
}
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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;
}
} }
} }

View File

@ -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;
} }
} }
} }

View File

@ -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,

View File

@ -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; }
} }
} }
} }

View File

@ -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;

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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
}; };

View File

@ -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);

View File

@ -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; }
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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);
} }

View File

@ -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);
} }

View File

@ -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" />

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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");

View File

@ -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; }
}
}

View File

@ -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>

View File

@ -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()

View File

@ -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);
} }
} }

View File

@ -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" />

View File

@ -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>

View File

@ -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()
{ {

View File

@ -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");

View File

@ -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();

View File

@ -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));

View File

@ -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"
}; };

View File

@ -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" : "";
}
} }
} }

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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>

View File

@ -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>();

View File

@ -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>

View File

@ -36,6 +36,7 @@ namespace MediaBrowser.Model.LiveTv
MediaLocationsCreated = new string[] { }; MediaLocationsCreated = new string[] { };
RecordingEncodingFormat = "mp4"; RecordingEncodingFormat = "mp4";
RecordingPostProcessorArguments = "\"{path}\""; RecordingPostProcessorArguments = "\"{path}\"";
EnableRecordingEncoding = true;
} }
} }

View File

@ -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" />

View 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; }
}
}

View File

@ -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; }
} }
} }

View File

@ -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

View File

@ -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);
}
}

View File

@ -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))
{ {

View File

@ -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>

View File

@ -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";

View File

@ -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>

View File

@ -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";

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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);

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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