commit
d2050ac305
|
@ -482,7 +482,14 @@ namespace MediaBrowser.Api.LiveTv
|
|||
[Authenticated(AllowBeforeStartupWizard = true)]
|
||||
public class GetSatIniMappings : IReturn<List<NameValuePair>>
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")]
|
||||
[Authenticated(AllowBeforeStartupWizard = true)]
|
||||
public class GetSatChannnelScanResult : TunerHostInfo
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class LiveTvService : BaseApiService
|
||||
|
@ -504,6 +511,13 @@ namespace MediaBrowser.Api.LiveTv
|
|||
_dtoService = dtoService;
|
||||
}
|
||||
|
||||
public async Task<object> Get(GetSatChannnelScanResult request)
|
||||
{
|
||||
var result = await _liveTvManager.GetSatChannelScanResult(request, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
public async Task<object> Get(GetLiveTvRegistrationInfo request)
|
||||
{
|
||||
var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false);
|
||||
|
|
|
@ -3,11 +3,11 @@ namespace MediaBrowser.Common.Implementations.Security
|
|||
{
|
||||
public class MbAdmin
|
||||
{
|
||||
public const string HttpUrl = "http://www.mb3admin.com/admin/";
|
||||
public const string HttpUrl = "https://www.mb3admin.com/admin/";
|
||||
|
||||
/// <summary>
|
||||
/// Leaving as http for now until we get it squared away
|
||||
/// </summary>
|
||||
public const string HttpsUrl = "http://www.mb3admin.com/admin/";
|
||||
public const string HttpsUrl = "https://www.mb3admin.com/admin/";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace MediaBrowser.Common.Implementations.Security
|
|||
public class PluginSecurityManager : ISecurityManager
|
||||
{
|
||||
private const string MBValidateUrl = MbAdmin.HttpsUrl + "service/registration/validate";
|
||||
private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "http://mb3admin.com/admin/service/appstore/register";
|
||||
private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "https://mb3admin.com/admin/service/appstore/register";
|
||||
|
||||
/// <summary>
|
||||
/// The _is MB supporter
|
||||
|
|
|
@ -255,6 +255,11 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
if (string.IsNullOrWhiteSpace(Path))
|
||||
{
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
return LocationType.Remote;
|
||||
}
|
||||
|
||||
return LocationType.Virtual;
|
||||
}
|
||||
|
||||
|
@ -494,7 +499,19 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
get
|
||||
{
|
||||
return _sortName ?? (_sortName = CreateSortName());
|
||||
if (_sortName == null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ForcedSortName))
|
||||
{
|
||||
// Need the ToLower because that's what CreateSortName does
|
||||
_sortName = ModifySortChunks(ForcedSortName).ToLower();
|
||||
}
|
||||
else
|
||||
{
|
||||
_sortName = CreateSortName();
|
||||
}
|
||||
}
|
||||
return _sortName;
|
||||
}
|
||||
set
|
||||
{
|
||||
|
@ -529,11 +546,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
/// <returns>System.String.</returns>
|
||||
protected virtual string CreateSortName()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ForcedSortName))
|
||||
{
|
||||
return ModifySortChunks(ForcedSortName).ToLower();
|
||||
}
|
||||
|
||||
if (Name == null) return null; //some items may not have name filled in properly
|
||||
|
||||
if (!EnableAlphaNumericSorting)
|
||||
|
|
|
@ -175,7 +175,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||
/// <returns>System.String.</returns>
|
||||
protected override string CreateSortName()
|
||||
{
|
||||
return (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("000-") : "")
|
||||
return (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("000 - ") : "")
|
||||
+ (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name;
|
||||
}
|
||||
|
||||
|
|
|
@ -79,23 +79,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
locationType != LocationType.Virtual;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override LocationType LocationType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
return LocationType.Remote;
|
||||
}
|
||||
}
|
||||
|
||||
return base.LocationType;
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override bool SupportsAddingToPlaylist
|
||||
{
|
||||
|
|
|
@ -383,5 +383,7 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// </summary>
|
||||
/// <returns>List<NameValuePair>.</returns>
|
||||
List<NameValuePair> GetSatIniMappings();
|
||||
|
||||
Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
|||
private Timer _notificationTimer;
|
||||
|
||||
private bool _isDisposed;
|
||||
private readonly ConcurrentDictionary<string, List<UpnpDevice>> _devices = new ConcurrentDictionary<string, List<UpnpDevice>>();
|
||||
private readonly Dictionary<string, List<UpnpDevice>> _devices = new Dictionary<string, List<UpnpDevice>>();
|
||||
|
||||
private readonly IApplicationHost _appHost;
|
||||
|
||||
|
@ -172,9 +172,12 @@ namespace MediaBrowser.Dlna.Ssdp
|
|||
{
|
||||
get
|
||||
{
|
||||
var devices = _devices.ToList();
|
||||
lock (_devices)
|
||||
{
|
||||
var devices = _devices.ToList();
|
||||
|
||||
return devices.SelectMany(i => i.Value).ToList();
|
||||
return devices.SelectMany(i => i.Value).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -482,26 +485,42 @@ namespace MediaBrowser.Dlna.Ssdp
|
|||
|
||||
public void RegisterNotification(string uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
|
||||
{
|
||||
var list = _devices.GetOrAdd(uuid, new List<UpnpDevice>());
|
||||
lock (_devices)
|
||||
{
|
||||
List<UpnpDevice> list;
|
||||
List<UpnpDevice> dl;
|
||||
if (_devices.TryGetValue(uuid, out dl))
|
||||
{
|
||||
list = dl;
|
||||
}
|
||||
else
|
||||
{
|
||||
list = new List<UpnpDevice>();
|
||||
_devices[uuid] = list;
|
||||
}
|
||||
|
||||
list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
|
||||
list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
|
||||
|
||||
NotifyAll();
|
||||
_logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri);
|
||||
NotifyAll();
|
||||
_logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterNotification(string uuid)
|
||||
{
|
||||
List<UpnpDevice> dl;
|
||||
if (_devices.TryRemove(uuid, out dl))
|
||||
lock (_devices)
|
||||
{
|
||||
|
||||
foreach (var d in dl.ToList())
|
||||
List<UpnpDevice> dl;
|
||||
if (_devices.TryGetValue(uuid, out dl))
|
||||
{
|
||||
NotifyDevice(d, "byebye", true);
|
||||
}
|
||||
_devices.Remove(uuid);
|
||||
foreach (var d in dl.ToList())
|
||||
{
|
||||
NotifyDevice(d, "byebye", true);
|
||||
}
|
||||
|
||||
_logger.Debug("Unregistered mount {0}", uuid);
|
||||
_logger.Debug("Unregistered mount {0}", uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@ namespace MediaBrowser.Model.LiveTv
|
|||
public string FriendlyName { get; set; }
|
||||
public int Tuners { get; set; }
|
||||
public string DiseqC { get; set; }
|
||||
public string SourceA { get; set; }
|
||||
public string SourceB { get; set; }
|
||||
public string SourceC { get; set; }
|
||||
public string SourceD { get; set; }
|
||||
|
||||
public int DataVersion { get; set; }
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration;
|
|||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using Mono.Nat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -11,6 +10,9 @@ using System.IO;
|
|||
using System.Net;
|
||||
using System.Text;
|
||||
using MediaBrowser.Common.Threading;
|
||||
using Open.Nat;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||
{
|
||||
|
@ -20,9 +22,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ISsdpHandler _ssdp;
|
||||
|
||||
private PeriodicTimer _timer;
|
||||
private bool _isStarted;
|
||||
private CancellationTokenSource _currentCancellationTokenSource;
|
||||
private TimeSpan _interval = TimeSpan.FromHours(1);
|
||||
|
||||
public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
|
||||
{
|
||||
|
@ -30,159 +31,78 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||
_appHost = appHost;
|
||||
_config = config;
|
||||
_ssdp = ssdp;
|
||||
|
||||
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
||||
}
|
||||
|
||||
private string _lastConfigIdentifier;
|
||||
private string GetConfigIdentifier()
|
||||
private void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
var values = new List<string>();
|
||||
var config = _config.Configuration;
|
||||
|
||||
values.Add(config.EnableUPnP.ToString());
|
||||
values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture));
|
||||
values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture));
|
||||
values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture));
|
||||
values.Add(config.EnableHttps.ToString());
|
||||
values.Add(_appHost.EnableHttps.ToString());
|
||||
|
||||
return string.Join("|", values.ToArray());
|
||||
}
|
||||
|
||||
void _config_ConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||
|
||||
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (_isStarted)
|
||||
{
|
||||
DisposeNat();
|
||||
}
|
||||
|
||||
Run();
|
||||
}
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
//NatUtility.Logger = new LogWriter(_logger);
|
||||
|
||||
if (_config.Configuration.EnableUPnP)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
|
||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
||||
Discover();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
private async void Discover()
|
||||
{
|
||||
_logger.Debug("Starting NAT discovery");
|
||||
NatUtility.EnabledProtocols = new List<NatProtocol>
|
||||
if (!_config.Configuration.EnableUPnP)
|
||||
{
|
||||
NatProtocol.Pmp
|
||||
};
|
||||
NatUtility.DeviceFound += NatUtility_DeviceFound;
|
||||
|
||||
// Mono.Nat does never rise this event. The event is there however it is useless.
|
||||
// You could remove it with no risk.
|
||||
NatUtility.DeviceLost += NatUtility_DeviceLost;
|
||||
|
||||
|
||||
// it is hard to say what one should do when an unhandled exception is raised
|
||||
// because there isn't anything one can do about it. Probably save a log or ignored it.
|
||||
NatUtility.UnhandledException += NatUtility_UnhandledException;
|
||||
NatUtility.StartDiscovery();
|
||||
|
||||
_timer = new PeriodicTimer(s => _createdRules = new List<string>(), null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
|
||||
_ssdp.MessageReceived += _ssdp_MessageReceived;
|
||||
|
||||
_lastConfigIdentifier = GetConfigIdentifier();
|
||||
|
||||
_isStarted = true;
|
||||
}
|
||||
|
||||
void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
|
||||
{
|
||||
var endpoint = e.EndPoint as IPEndPoint;
|
||||
|
||||
if (endpoint != null && e.LocalEndPoint != null)
|
||||
{
|
||||
NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = e.ExceptionObject as Exception;
|
||||
var discoverer = new NatDiscoverer();
|
||||
|
||||
if (ex == null)
|
||||
{
|
||||
//_logger.Error("Unidentified error reported by Mono.Nat");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Seeing some blank exceptions coming through here
|
||||
//_logger.ErrorException("Error reported by Mono.Nat: ", ex);
|
||||
}
|
||||
}
|
||||
var cancellationTokenSource = new CancellationTokenSource(10000);
|
||||
_currentCancellationTokenSource = cancellationTokenSource;
|
||||
|
||||
void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var device = e.Device;
|
||||
_logger.Debug("NAT device found: {0}", device.LocalAddress.ToString());
|
||||
var device = await discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cancellationTokenSource).ConfigureAwait(false);
|
||||
|
||||
await CreateRules(device).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
CreateRules(device);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// I think it could be a good idea to log the exception because
|
||||
// you are using permanent portmapping here (never expire) and that means that next time
|
||||
// CreatePortMap is invoked it can fails with a 718-ConflictInMappingEntry or not. That depends
|
||||
// on the router's upnp implementation (specs says it should fail however some routers don't do it)
|
||||
// It also can fail with others like 727-ExternalPortOnlySupportsWildcard, 728-NoPortMapsAvailable
|
||||
// and those errors (upnp errors) could be useful for diagnosting.
|
||||
_logger.ErrorException("Error discovering NAT devices", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentCancellationTokenSource = null;
|
||||
}
|
||||
|
||||
// Commenting out because users are reporting problems out of our control
|
||||
//_logger.ErrorException("Error creating port forwarding rules", ex);
|
||||
if (_config.Configuration.EnableUPnP)
|
||||
{
|
||||
await Task.Delay(_interval).ConfigureAwait(false);
|
||||
Discover();
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> _createdRules = new List<string>();
|
||||
private void CreateRules(INatDevice device)
|
||||
private async Task CreateRules(NatDevice device)
|
||||
{
|
||||
// On some systems the device discovered event seems to fire repeatedly
|
||||
// This check will help ensure we're not trying to port map the same device over and over
|
||||
|
||||
var address = device.LocalAddress.ToString();
|
||||
|
||||
if (!_createdRules.Contains(address))
|
||||
{
|
||||
_createdRules.Add(address);
|
||||
|
||||
CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
|
||||
CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
|
||||
}
|
||||
await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
|
||||
await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void CreatePortMap(INatDevice device, int privatePort, int publicPort)
|
||||
private async Task CreatePortMap(NatDevice device, int privatePort, int publicPort)
|
||||
{
|
||||
_logger.Debug("Creating port map on port {0}", privatePort);
|
||||
device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
|
||||
{
|
||||
Description = _appHost.Name
|
||||
});
|
||||
}
|
||||
|
||||
// As I said before, this method will be never invoked. You can remove it.
|
||||
void NatUtility_DeviceLost(object sender, DeviceEventArgs e)
|
||||
{
|
||||
var device = e.Device;
|
||||
_logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString());
|
||||
try
|
||||
{
|
||||
await device.CreatePortMapAsync(new Mapping(Protocol.Tcp, privatePort, publicPort, _appHost.Name)).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error creating port map", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -192,63 +112,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||
|
||||
private void DisposeNat()
|
||||
{
|
||||
_logger.Debug("Stopping NAT discovery");
|
||||
|
||||
if (_timer != null)
|
||||
if (_currentCancellationTokenSource != null)
|
||||
{
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
_ssdp.MessageReceived -= _ssdp_MessageReceived;
|
||||
|
||||
try
|
||||
{
|
||||
// This is not a significant improvement
|
||||
NatUtility.StopDiscovery();
|
||||
NatUtility.DeviceFound -= NatUtility_DeviceFound;
|
||||
NatUtility.DeviceLost -= NatUtility_DeviceLost;
|
||||
NatUtility.UnhandledException -= NatUtility_UnhandledException;
|
||||
}
|
||||
// Statements in try-block will no fail because StopDiscovery is a one-line
|
||||
// method that was no chances to fail.
|
||||
// public static void StopDiscovery ()
|
||||
// {
|
||||
// searching.Reset();
|
||||
// }
|
||||
// IMO you could remove the catch-block
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error stopping NAT Discovery", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class LogWriter : TextWriter
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public LogWriter(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override Encoding Encoding
|
||||
{
|
||||
get { return Encoding.UTF8; }
|
||||
}
|
||||
|
||||
public override void WriteLine(string format, params object[] arg)
|
||||
{
|
||||
_logger.Debug(format, arg);
|
||||
}
|
||||
|
||||
public override void WriteLine(string value)
|
||||
{
|
||||
_logger.Debug(value);
|
||||
try
|
||||
{
|
||||
_currentCancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error calling _currentCancellationTokenSource.Cancel", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||
private readonly IHttpClient _httpClient;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
private const string MbAdminUrl = "http://www.mb3admin.com/admin/";
|
||||
private const string MbAdminUrl = "https://www.mb3admin.com/admin/";
|
||||
|
||||
public UsageReporter(IApplicationHost applicationHost, IHttpClient httpClient, IUserManager userManager, ILogger logger)
|
||||
{
|
||||
|
|
|
@ -2450,7 +2450,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
|||
|
||||
public List<NameValuePair> GetSatIniMappings()
|
||||
{
|
||||
var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini.satellite", StringComparison.OrdinalIgnoreCase) != -1).ToList();
|
||||
var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini", StringComparison.OrdinalIgnoreCase) != -1).ToList();
|
||||
|
||||
return names.Select(GetSatIniMappings).Where(i => i != null).DistinctBy(i => i.Value.Split('|')[0]).ToList();
|
||||
}
|
||||
|
@ -2472,13 +2472,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
|||
return null;
|
||||
}
|
||||
|
||||
var srch = "SatIp.ini.";
|
||||
var filename = Path.GetFileName(resource);
|
||||
|
||||
return new NameValuePair
|
||||
{
|
||||
Name = satType1 + " " + satType2,
|
||||
Value = satType2 + "|" + Path.GetFileName(resource)
|
||||
Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard RTSP request methods.
|
||||
/// </summary>
|
||||
public sealed class RtspMethod
|
||||
{
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (_name != null ? _name.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
private readonly string _name;
|
||||
private static readonly IDictionary<string, RtspMethod> _values = new Dictionary<string, RtspMethod>();
|
||||
|
||||
public static readonly RtspMethod Describe = new RtspMethod("DESCRIBE");
|
||||
public static readonly RtspMethod Announce = new RtspMethod("ANNOUNCE");
|
||||
public static readonly RtspMethod GetParameter = new RtspMethod("GET_PARAMETER");
|
||||
public static readonly RtspMethod Options = new RtspMethod("OPTIONS");
|
||||
public static readonly RtspMethod Pause = new RtspMethod("PAUSE");
|
||||
public static readonly RtspMethod Play = new RtspMethod("PLAY");
|
||||
public static readonly RtspMethod Record = new RtspMethod("RECORD");
|
||||
public static readonly RtspMethod Redirect = new RtspMethod("REDIRECT");
|
||||
public static readonly RtspMethod Setup = new RtspMethod("SETUP");
|
||||
public static readonly RtspMethod SetParameter = new RtspMethod("SET_PARAMETER");
|
||||
public static readonly RtspMethod Teardown = new RtspMethod("TEARDOWN");
|
||||
|
||||
private RtspMethod(string name)
|
||||
{
|
||||
_name = name;
|
||||
_values.Add(name, this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var method = obj as RtspMethod;
|
||||
if (method != null && this == method)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ICollection<RtspMethod> Values
|
||||
{
|
||||
get { return _values.Values; }
|
||||
}
|
||||
|
||||
public static explicit operator RtspMethod(string name)
|
||||
{
|
||||
RtspMethod value;
|
||||
if (!_values.TryGetValue(name, out value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static implicit operator string(RtspMethod method)
|
||||
{
|
||||
return method._name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
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.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple class that can be used to serialise RTSP requests.
|
||||
/// </summary>
|
||||
public class RtspRequest
|
||||
{
|
||||
private readonly RtspMethod _method;
|
||||
private readonly string _uri;
|
||||
private readonly int _majorVersion;
|
||||
private readonly int _minorVersion;
|
||||
private IDictionary<string, string> _headers = new Dictionary<string, string>();
|
||||
private string _body = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of the <see cref="RtspRequest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="method">The request method.</param>
|
||||
/// <param name="uri">The request URI</param>
|
||||
/// <param name="majorVersion">The major version number.</param>
|
||||
/// <param name="minorVersion">The minor version number.</param>
|
||||
public RtspRequest(RtspMethod method, string uri, int majorVersion, int minorVersion)
|
||||
{
|
||||
_method = method;
|
||||
_uri = uri;
|
||||
_majorVersion = majorVersion;
|
||||
_minorVersion = minorVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the request method.
|
||||
/// </summary>
|
||||
public RtspMethod Method
|
||||
{
|
||||
get
|
||||
{
|
||||
return _method;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the request URI.
|
||||
/// </summary>
|
||||
public string Uri
|
||||
{
|
||||
get
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the request major version number.
|
||||
/// </summary>
|
||||
public int MajorVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return _majorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the request minor version number.
|
||||
/// </summary>
|
||||
public int MinorVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return _minorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the request headers.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> Headers
|
||||
{
|
||||
get
|
||||
{
|
||||
return _headers;
|
||||
}
|
||||
set
|
||||
{
|
||||
_headers = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the request body.
|
||||
/// </summary>
|
||||
public string Body
|
||||
{
|
||||
get
|
||||
{
|
||||
return _body;
|
||||
}
|
||||
set
|
||||
{
|
||||
_body = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialise this request.
|
||||
/// </summary>
|
||||
/// <returns>raw request bytes</returns>
|
||||
public byte[] Serialise()
|
||||
{
|
||||
var request = new StringBuilder();
|
||||
request.AppendFormat("{0} {1} RTSP/{2}.{3}\r\n", _method, _uri, _majorVersion, _minorVersion);
|
||||
foreach (var header in _headers)
|
||||
{
|
||||
request.AppendFormat("{0}: {1}\r\n", header.Key, header.Value);
|
||||
}
|
||||
request.AppendFormat("\r\n{0}", _body);
|
||||
return Encoding.UTF8.GetBytes(request.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple class that can be used to deserialise RTSP responses.
|
||||
/// </summary>
|
||||
public class RtspResponse
|
||||
{
|
||||
private static readonly Regex RegexStatusLine = new Regex(@"RTSP/(\d+)\.(\d+)\s+(\d+)\s+([^.]+?)\r\n(.*)", RegexOptions.Singleline);
|
||||
|
||||
private int _majorVersion = 1;
|
||||
private int _minorVersion;
|
||||
private RtspStatusCode _statusCode;
|
||||
private string _reasonPhrase;
|
||||
private IDictionary<string, string> _headers;
|
||||
private string _body;
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of the <see cref="RtspResponse"/> class.
|
||||
/// </summary>
|
||||
private RtspResponse()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the response major version number.
|
||||
/// </summary>
|
||||
public int MajorVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return _majorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the response minor version number.
|
||||
/// </summary>
|
||||
public int MinorVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return _minorVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the response status code.
|
||||
/// </summary>
|
||||
public RtspStatusCode StatusCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the response reason phrase.
|
||||
/// </summary>
|
||||
public string ReasonPhrase
|
||||
{
|
||||
get
|
||||
{
|
||||
return _reasonPhrase;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the response headers.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> Headers
|
||||
{
|
||||
get
|
||||
{
|
||||
return _headers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the response body.
|
||||
/// </summary>
|
||||
public string Body
|
||||
{
|
||||
get
|
||||
{
|
||||
return _body;
|
||||
}
|
||||
set
|
||||
{
|
||||
_body = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialise/parse an RTSP response.
|
||||
/// </summary>
|
||||
/// <param name="responseBytes">The raw response bytes.</param>
|
||||
/// <param name="responseByteCount">The number of valid bytes in the response.</param>
|
||||
/// <returns>a response object</returns>
|
||||
public static RtspResponse Deserialise(byte[] responseBytes, int responseByteCount)
|
||||
{
|
||||
var response = new RtspResponse();
|
||||
var responseString = Encoding.UTF8.GetString(responseBytes, 0, responseByteCount);
|
||||
|
||||
var m = RegexStatusLine.Match(responseString);
|
||||
if (m.Success)
|
||||
{
|
||||
response._majorVersion = int.Parse(m.Groups[1].Captures[0].Value);
|
||||
response._minorVersion = int.Parse(m.Groups[2].Captures[0].Value);
|
||||
response._statusCode = (RtspStatusCode)int.Parse(m.Groups[3].Captures[0].Value);
|
||||
response._reasonPhrase = m.Groups[4].Captures[0].Value;
|
||||
responseString = m.Groups[5].Captures[0].Value;
|
||||
}
|
||||
|
||||
var sections = responseString.Split(new[] { "\r\n\r\n" }, StringSplitOptions.None);
|
||||
response._body = sections[1];
|
||||
var headers = sections[0].Split(new[] { "\r\n" }, StringSplitOptions.None);
|
||||
response._headers = new Dictionary<string, string>();
|
||||
foreach (var headerInfo in headers.Select(header => header.Split(':')))
|
||||
{
|
||||
response._headers.Add(headerInfo[0], headerInfo[1].Trim());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,688 @@
|
|||
/*
|
||||
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.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
|
||||
{
|
||||
public class RtspSession : IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
private static readonly Regex RegexRtspSessionHeader = new Regex(@"\s*([^\s;]+)(;timeout=(\d+))?");
|
||||
private const int DefaultRtspSessionTimeout = 30; // unit = s
|
||||
private static readonly Regex RegexDescribeResponseSignalInfo = new Regex(@";tuner=\d+,(\d+),(\d+),(\d+),", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||
private string _address;
|
||||
private string _rtspSessionId;
|
||||
|
||||
public string RtspSessionId
|
||||
{
|
||||
get { return _rtspSessionId; }
|
||||
set { _rtspSessionId = value; }
|
||||
}
|
||||
private int _rtspSessionTimeToLive = 0;
|
||||
private string _rtspStreamId;
|
||||
private int _clientRtpPort;
|
||||
private int _clientRtcpPort;
|
||||
private int _serverRtpPort;
|
||||
private int _serverRtcpPort;
|
||||
private int _rtpPort;
|
||||
private int _rtcpPort;
|
||||
private string _rtspStreamUrl;
|
||||
private string _destination;
|
||||
private string _source;
|
||||
private string _transport;
|
||||
private int _signalLevel;
|
||||
private int _signalQuality;
|
||||
private Socket _rtspSocket;
|
||||
private int _rtspSequenceNum = 1;
|
||||
private bool _disposed = false;
|
||||
private readonly ILogger _logger;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public RtspSession(string address, ILogger logger)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address))
|
||||
{
|
||||
throw new ArgumentNullException("address");
|
||||
}
|
||||
|
||||
_address = address;
|
||||
_logger = logger;
|
||||
|
||||
_logger.Info("Creating RtspSession with url {0}", address);
|
||||
}
|
||||
~RtspSession()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
#region Rtsp
|
||||
|
||||
public string RtspStreamId
|
||||
{
|
||||
get { return _rtspStreamId; }
|
||||
set { if (_rtspStreamId != value) { _rtspStreamId = value; OnPropertyChanged("RtspStreamId"); } }
|
||||
}
|
||||
public string RtspStreamUrl
|
||||
{
|
||||
get { return _rtspStreamUrl; }
|
||||
set { if (_rtspStreamUrl != value) { _rtspStreamUrl = value; OnPropertyChanged("RtspStreamUrl"); } }
|
||||
}
|
||||
|
||||
public int RtspSessionTimeToLive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rtspSessionTimeToLive == 0)
|
||||
_rtspSessionTimeToLive = DefaultRtspSessionTimeout;
|
||||
return _rtspSessionTimeToLive * 1000 - 20;
|
||||
}
|
||||
set { if (_rtspSessionTimeToLive != value) { _rtspSessionTimeToLive = value; OnPropertyChanged("RtspSessionTimeToLive"); } }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rtp Rtcp
|
||||
|
||||
/// <summary>
|
||||
/// The LocalEndPoint Address
|
||||
/// </summary>
|
||||
public string Destination
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_destination))
|
||||
{
|
||||
var result = "";
|
||||
var host = Dns.GetHostName();
|
||||
var hostentry = Dns.GetHostEntry(host);
|
||||
foreach (var ip in hostentry.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork))
|
||||
{
|
||||
result = ip.ToString();
|
||||
}
|
||||
|
||||
_destination = result;
|
||||
}
|
||||
return _destination;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_destination != value)
|
||||
{
|
||||
_destination = value;
|
||||
OnPropertyChanged("Destination");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The RemoteEndPoint Address
|
||||
/// </summary>
|
||||
public string Source
|
||||
{
|
||||
get { return _source; }
|
||||
set
|
||||
{
|
||||
if (_source != value)
|
||||
{
|
||||
_source = value;
|
||||
OnPropertyChanged("Source");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Media Data Delivery RemoteEndPoint Port if we use Unicast
|
||||
/// </summary>
|
||||
public int ServerRtpPort
|
||||
{
|
||||
get
|
||||
{
|
||||
return _serverRtpPort;
|
||||
}
|
||||
set { if (_serverRtpPort != value) { _serverRtpPort = value; OnPropertyChanged("ServerRtpPort"); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Media Metadata Delivery RemoteEndPoint Port if we use Unicast
|
||||
/// </summary>
|
||||
public int ServerRtcpPort
|
||||
{
|
||||
get { return _serverRtcpPort; }
|
||||
set { if (_serverRtcpPort != value) { _serverRtcpPort = value; OnPropertyChanged("ServerRtcpPort"); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Media Data Delivery LocalEndPoint Port if we use Unicast
|
||||
/// </summary>
|
||||
public int ClientRtpPort
|
||||
{
|
||||
get { return _clientRtpPort; }
|
||||
set { if (_clientRtpPort != value) { _clientRtpPort = value; OnPropertyChanged("ClientRtpPort"); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Media Metadata Delivery LocalEndPoint Port if we use Unicast
|
||||
/// </summary>
|
||||
public int ClientRtcpPort
|
||||
{
|
||||
get { return _clientRtcpPort; }
|
||||
set { if (_clientRtcpPort != value) { _clientRtcpPort = value; OnPropertyChanged("ClientRtcpPort"); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Media Data Delivery RemoteEndPoint Port if we use Multicast
|
||||
/// </summary>
|
||||
public int RtpPort
|
||||
{
|
||||
get { return _rtpPort; }
|
||||
set { if (_rtpPort != value) { _rtpPort = value; OnPropertyChanged("RtpPort"); } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Media Meta Delivery RemoteEndPoint Port if we use Multicast
|
||||
/// </summary>
|
||||
public int RtcpPort
|
||||
{
|
||||
get { return _rtcpPort; }
|
||||
set { if (_rtcpPort != value) { _rtcpPort = value; OnPropertyChanged("RtcpPort"); } }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public string Transport
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_transport))
|
||||
{
|
||||
_transport = "unicast";
|
||||
}
|
||||
return _transport;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_transport != value)
|
||||
{
|
||||
_transport = value;
|
||||
OnPropertyChanged("Transport");
|
||||
}
|
||||
}
|
||||
}
|
||||
public int SignalLevel
|
||||
{
|
||||
get { return _signalLevel; }
|
||||
set { if (_signalLevel != value) { _signalLevel = value; OnPropertyChanged("SignalLevel"); } }
|
||||
}
|
||||
public int SignalQuality
|
||||
{
|
||||
get { return _signalQuality; }
|
||||
set { if (_signalQuality != value) { _signalQuality = value; OnPropertyChanged("SignalQuality"); } }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void ProcessSessionHeader(string sessionHeader, string response)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sessionHeader))
|
||||
{
|
||||
var m = RegexRtspSessionHeader.Match(sessionHeader);
|
||||
if (!m.Success)
|
||||
{
|
||||
_logger.Error("Failed to tune, RTSP {0} response session header {1} format not recognised", response, sessionHeader);
|
||||
}
|
||||
_rtspSessionId = m.Groups[1].Captures[0].Value;
|
||||
_rtspSessionTimeToLive = m.Groups[3].Captures.Count == 1 ? int.Parse(m.Groups[3].Captures[0].Value) : DefaultRtspSessionTimeout;
|
||||
}
|
||||
}
|
||||
private void ProcessTransportHeader(string transportHeader)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(transportHeader))
|
||||
{
|
||||
var transports = transportHeader.Split(',');
|
||||
foreach (var transport in transports)
|
||||
{
|
||||
if (transport.Trim().StartsWith("RTP/AVP"))
|
||||
{
|
||||
var sections = transport.Split(';');
|
||||
foreach (var section in sections)
|
||||
{
|
||||
var parts = section.Split('=');
|
||||
if (parts[0].Equals("server_port"))
|
||||
{
|
||||
var ports = parts[1].Split('-');
|
||||
_serverRtpPort = int.Parse(ports[0]);
|
||||
_serverRtcpPort = int.Parse(ports[1]);
|
||||
}
|
||||
else if (parts[0].Equals("destination"))
|
||||
{
|
||||
_destination = parts[1];
|
||||
}
|
||||
else if (parts[0].Equals("port"))
|
||||
{
|
||||
var ports = parts[1].Split('-');
|
||||
_rtpPort = int.Parse(ports[0]);
|
||||
_rtcpPort = int.Parse(ports[1]);
|
||||
}
|
||||
else if (parts[0].Equals("ttl"))
|
||||
{
|
||||
_rtspSessionTimeToLive = int.Parse(parts[1]);
|
||||
}
|
||||
else if (parts[0].Equals("source"))
|
||||
{
|
||||
_source = parts[1];
|
||||
}
|
||||
else if (parts[0].Equals("client_port"))
|
||||
{
|
||||
var ports = parts[1].Split('-');
|
||||
var rtp = int.Parse(ports[0]);
|
||||
var rtcp = int.Parse(ports[1]);
|
||||
//if (!rtp.Equals(_rtpPort))
|
||||
//{
|
||||
// Logger.Error("SAT>IP base: server specified RTP client port {0} instead of {1}", rtp, _rtpPort);
|
||||
//}
|
||||
//if (!rtcp.Equals(_rtcpPort))
|
||||
//{
|
||||
// Logger.Error("SAT>IP base: server specified RTCP client port {0} instead of {1}", rtcp, _rtcpPort);
|
||||
//}
|
||||
_rtpPort = rtp;
|
||||
_rtcpPort = rtcp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void Connect()
|
||||
{
|
||||
_rtspSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
var ip = IPAddress.Parse(_address);
|
||||
var rtspEndpoint = new IPEndPoint(ip, 554);
|
||||
_rtspSocket.Connect(rtspEndpoint);
|
||||
}
|
||||
private void Disconnect()
|
||||
{
|
||||
if (_rtspSocket != null && _rtspSocket.Connected)
|
||||
{
|
||||
_rtspSocket.Shutdown(SocketShutdown.Both);
|
||||
_rtspSocket.Close();
|
||||
}
|
||||
}
|
||||
private void SendRequest(RtspRequest request)
|
||||
{
|
||||
if (_rtspSocket == null)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
try
|
||||
{
|
||||
request.Headers.Add("CSeq", _rtspSequenceNum.ToString());
|
||||
_rtspSequenceNum++;
|
||||
byte[] requestBytes = request.Serialise();
|
||||
if (_rtspSocket != null)
|
||||
{
|
||||
var requestBytesCount = _rtspSocket.Send(requestBytes, requestBytes.Length, SocketFlags.None);
|
||||
if (requestBytesCount < 1)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e.Message);
|
||||
}
|
||||
}
|
||||
private void ReceiveResponse(out RtspResponse response)
|
||||
{
|
||||
response = null;
|
||||
var responseBytesCount = 0;
|
||||
byte[] responseBytes = new byte[1024];
|
||||
try
|
||||
{
|
||||
responseBytesCount = _rtspSocket.Receive(responseBytes, responseBytes.Length, SocketFlags.None);
|
||||
response = RtspResponse.Deserialise(responseBytes, responseBytesCount);
|
||||
string contentLengthString;
|
||||
int contentLength = 0;
|
||||
if (response.Headers.TryGetValue("Content-Length", out contentLengthString))
|
||||
{
|
||||
contentLength = int.Parse(contentLengthString);
|
||||
if ((string.IsNullOrEmpty(response.Body) && contentLength > 0) || response.Body.Length < contentLength)
|
||||
{
|
||||
if (response.Body == null)
|
||||
{
|
||||
response.Body = string.Empty;
|
||||
}
|
||||
while (responseBytesCount > 0 && response.Body.Length < contentLength)
|
||||
{
|
||||
responseBytesCount = _rtspSocket.Receive(responseBytes, responseBytes.Length, SocketFlags.None);
|
||||
response.Body += System.Text.Encoding.UTF8.GetString(responseBytes, 0, responseBytesCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public RtspStatusCode Setup(string query, string transporttype)
|
||||
{
|
||||
|
||||
RtspRequest request;
|
||||
RtspResponse response;
|
||||
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
|
||||
if ((_rtspSocket == null))
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
if (string.IsNullOrEmpty(_rtspSessionId))
|
||||
{
|
||||
request = new RtspRequest(RtspMethod.Setup, string.Format("rtsp://{0}:{1}/?{2}", _address, 554, query), 1, 0);
|
||||
switch (transporttype)
|
||||
{
|
||||
case "multicast":
|
||||
request.Headers.Add("Transport", string.Format("RTP/AVP;multicast"));
|
||||
break;
|
||||
case "unicast":
|
||||
var activeTcpConnections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections();
|
||||
var usedPorts = new HashSet<int>();
|
||||
foreach (var connection in activeTcpConnections)
|
||||
{
|
||||
usedPorts.Add(connection.LocalEndPoint.Port);
|
||||
}
|
||||
for (var port = 40000; port <= 65534; port += 2)
|
||||
{
|
||||
if (!usedPorts.Contains(port) && !usedPorts.Contains(port + 1))
|
||||
{
|
||||
|
||||
_clientRtpPort = port;
|
||||
_clientRtcpPort = port + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
request.Headers.Add("Transport", string.Format("RTP/AVP;unicast;client_port={0}-{1}", _clientRtpPort, _clientRtcpPort));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
request = new RtspRequest(RtspMethod.Setup, string.Format("rtsp://{0}:{1}/?{2}", _address, 554, query), 1, 0);
|
||||
switch (transporttype)
|
||||
{
|
||||
case "multicast":
|
||||
request.Headers.Add("Transport", string.Format("RTP/AVP;multicast"));
|
||||
break;
|
||||
case "unicast":
|
||||
request.Headers.Add("Transport", string.Format("RTP/AVP;unicast;client_port={0}-{1}", _clientRtpPort, _clientRtcpPort));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
SendRequest(request);
|
||||
ReceiveResponse(out response);
|
||||
|
||||
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
|
||||
//{
|
||||
// Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
|
||||
//}
|
||||
if (!response.Headers.TryGetValue("com.ses.streamID", out _rtspStreamId))
|
||||
{
|
||||
_logger.Error(string.Format("Failed to tune, not able to locate Stream ID header in RTSP SETUP response"));
|
||||
}
|
||||
string sessionHeader;
|
||||
if (!response.Headers.TryGetValue("Session", out sessionHeader))
|
||||
{
|
||||
_logger.Error(string.Format("Failed to tune, not able to locate Session header in RTSP SETUP response"));
|
||||
}
|
||||
ProcessSessionHeader(sessionHeader, "Setup");
|
||||
string transportHeader;
|
||||
if (!response.Headers.TryGetValue("Transport", out transportHeader))
|
||||
{
|
||||
_logger.Error(string.Format("Failed to tune, not able to locate Transport header in RTSP SETUP response"));
|
||||
}
|
||||
ProcessTransportHeader(transportHeader);
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
public RtspStatusCode Play(string query)
|
||||
{
|
||||
if ((_rtspSocket == null))
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
|
||||
RtspResponse response;
|
||||
string data;
|
||||
if (string.IsNullOrEmpty(query))
|
||||
{
|
||||
data = string.Format("rtsp://{0}:{1}/stream={2}", _address,
|
||||
554, _rtspStreamId);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = string.Format("rtsp://{0}:{1}/stream={2}?{3}", _address,
|
||||
554, _rtspStreamId, query);
|
||||
}
|
||||
var request = new RtspRequest(RtspMethod.Play, data, 1, 0);
|
||||
request.Headers.Add("Session", _rtspSessionId);
|
||||
SendRequest(request);
|
||||
ReceiveResponse(out response);
|
||||
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
|
||||
//{
|
||||
// Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
|
||||
//}
|
||||
//Logger.Info("RtspSession-Play : \r\n {0}", response);
|
||||
string sessionHeader;
|
||||
if (!response.Headers.TryGetValue("Session", out sessionHeader))
|
||||
{
|
||||
_logger.Error(string.Format("Failed to tune, not able to locate Session header in RTSP Play response"));
|
||||
}
|
||||
ProcessSessionHeader(sessionHeader, "Play");
|
||||
string rtpinfoHeader;
|
||||
if (!response.Headers.TryGetValue("RTP-Info", out rtpinfoHeader))
|
||||
{
|
||||
_logger.Error(string.Format("Failed to tune, not able to locate Rtp-Info header in RTSP Play response"));
|
||||
}
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
public RtspStatusCode Options()
|
||||
{
|
||||
if ((_rtspSocket == null))
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
|
||||
RtspRequest request;
|
||||
RtspResponse response;
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(_rtspSessionId))
|
||||
{
|
||||
request = new RtspRequest(RtspMethod.Options, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
request = new RtspRequest(RtspMethod.Options, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
|
||||
request.Headers.Add("Session", _rtspSessionId);
|
||||
}
|
||||
SendRequest(request);
|
||||
ReceiveResponse(out response);
|
||||
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
|
||||
//{
|
||||
// Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
|
||||
//}
|
||||
//Logger.Info("RtspSession-Options : \r\n {0}", response);
|
||||
string sessionHeader;
|
||||
if (!response.Headers.TryGetValue("Session", out sessionHeader))
|
||||
{
|
||||
_logger.Error(string.Format("Failed to tune, not able to locate session header in RTSP Options response"));
|
||||
}
|
||||
ProcessSessionHeader(sessionHeader, "Options");
|
||||
string optionsHeader;
|
||||
if (!response.Headers.TryGetValue("Public", out optionsHeader))
|
||||
{
|
||||
_logger.Error(string.Format("Failed to tune, not able to Options header in RTSP Options response"));
|
||||
}
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
public RtspStatusCode Describe(out int level, out int quality)
|
||||
{
|
||||
if ((_rtspSocket == null))
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
|
||||
RtspRequest request;
|
||||
RtspResponse response;
|
||||
level = 0;
|
||||
quality = 0;
|
||||
|
||||
if (string.IsNullOrEmpty(_rtspSessionId))
|
||||
{
|
||||
request = new RtspRequest(RtspMethod.Describe, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
|
||||
request.Headers.Add("Accept", "application/sdp");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
request = new RtspRequest(RtspMethod.Describe, string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId), 1, 0);
|
||||
request.Headers.Add("Accept", "application/sdp");
|
||||
request.Headers.Add("Session", _rtspSessionId);
|
||||
}
|
||||
SendRequest(request);
|
||||
ReceiveResponse(out response);
|
||||
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
|
||||
//{
|
||||
// Logger.Error("Failed to tune, non-OK RTSP Describe status code {0} {1}", response.StatusCode, response.ReasonPhrase);
|
||||
//}
|
||||
//Logger.Info("RtspSession-Describe : \r\n {0}", response);
|
||||
string sessionHeader;
|
||||
if (!response.Headers.TryGetValue("Session", out sessionHeader))
|
||||
{
|
||||
_logger.Error(string.Format("Failed to tune, not able to locate session header in RTSP Describe response"));
|
||||
}
|
||||
ProcessSessionHeader(sessionHeader, "Describe");
|
||||
var m = RegexDescribeResponseSignalInfo.Match(response.Body);
|
||||
if (m.Success)
|
||||
{
|
||||
|
||||
//isSignalLocked = m.Groups[2].Captures[0].Value.Equals("1");
|
||||
level = int.Parse(m.Groups[1].Captures[0].Value) * 100 / 255; // level: 0..255 => 0..100
|
||||
quality = int.Parse(m.Groups[3].Captures[0].Value) * 100 / 15; // quality: 0..15 => 0..100
|
||||
|
||||
}
|
||||
/*
|
||||
v=0
|
||||
o=- 1378633020884883 1 IN IP4 192.168.2.108
|
||||
s=SatIPServer:1 4
|
||||
t=0 0
|
||||
a=tool:idl4k
|
||||
m=video 52780 RTP/AVP 33
|
||||
c=IN IP4 0.0.0.0
|
||||
b=AS:5000
|
||||
a=control:stream=4
|
||||
a=fmtp:33 ver=1.0;tuner=1,0,0,0,12344,h,dvbs2,,off,,22000,34;pids=0,100,101,102,103,106
|
||||
=sendonly
|
||||
*/
|
||||
|
||||
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
public RtspStatusCode TearDown()
|
||||
{
|
||||
if ((_rtspSocket == null))
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
|
||||
RtspResponse response;
|
||||
|
||||
var request = new RtspRequest(RtspMethod.Teardown, string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId), 1, 0);
|
||||
request.Headers.Add("Session", _rtspSessionId);
|
||||
SendRequest(request);
|
||||
ReceiveResponse(out response);
|
||||
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
|
||||
//{
|
||||
// Logger.Error("Failed to tune, non-OK RTSP Teardown status code {0} {1}", response.StatusCode, response.ReasonPhrase);
|
||||
//}
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Events
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected void OnPropertyChanged(string name)
|
||||
{
|
||||
//var handler = PropertyChanged;
|
||||
//if (handler != null)
|
||||
//{
|
||||
// handler(this, new PropertyChangedEventArgs(name));
|
||||
//}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);//Disconnect();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
TearDown();
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
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.ComponentModel;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard RTSP status codes.
|
||||
/// </summary>
|
||||
public enum RtspStatusCode
|
||||
{
|
||||
/// <summary>
|
||||
/// 100 continue
|
||||
/// </summary>
|
||||
Continue = 100,
|
||||
|
||||
/// <summary>
|
||||
/// 200 OK
|
||||
/// </summary>
|
||||
[Description("Okay")]
|
||||
Ok = 200,
|
||||
/// <summary>
|
||||
/// 201 created
|
||||
/// </summary>
|
||||
Created = 201,
|
||||
|
||||
/// <summary>
|
||||
/// 250 low on storage space
|
||||
/// </summary>
|
||||
[Description("Low On Storage Space")]
|
||||
LowOnStorageSpace = 250,
|
||||
|
||||
/// <summary>
|
||||
/// 300 multiple choices
|
||||
/// </summary>
|
||||
[Description("Multiple Choices")]
|
||||
MultipleChoices = 300,
|
||||
/// <summary>
|
||||
/// 301 moved permanently
|
||||
/// </summary>
|
||||
[Description("Moved Permanently")]
|
||||
MovedPermanently = 301,
|
||||
/// <summary>
|
||||
/// 302 moved temporarily
|
||||
/// </summary>
|
||||
[Description("Moved Temporarily")]
|
||||
MovedTemporarily = 302,
|
||||
/// <summary>
|
||||
/// 303 see other
|
||||
/// </summary>
|
||||
[Description("See Other")]
|
||||
SeeOther = 303,
|
||||
/// <summary>
|
||||
/// 304 not modified
|
||||
/// </summary>
|
||||
[Description("Not Modified")]
|
||||
NotModified = 304,
|
||||
/// <summary>
|
||||
/// 305 use proxy
|
||||
/// </summary>
|
||||
[Description("Use Proxy")]
|
||||
UseProxy = 305,
|
||||
|
||||
/// <summary>
|
||||
/// 400 bad request
|
||||
/// </summary>
|
||||
[Description("Bad Request")]
|
||||
BadRequest = 400,
|
||||
/// <summary>
|
||||
/// 401 unauthorised
|
||||
/// </summary>
|
||||
Unauthorised = 401,
|
||||
/// <summary>
|
||||
/// 402 payment required
|
||||
/// </summary>
|
||||
[Description("Payment Required")]
|
||||
PaymentRequired = 402,
|
||||
/// <summary>
|
||||
/// 403 forbidden
|
||||
/// </summary>
|
||||
Forbidden = 403,
|
||||
/// <summary>
|
||||
/// 404 not found
|
||||
/// </summary>
|
||||
[Description("Not Found")]
|
||||
NotFound = 404,
|
||||
/// <summary>
|
||||
/// 405 method not allowed
|
||||
/// </summary>
|
||||
[Description("Method Not Allowed")]
|
||||
MethodNotAllowed = 405,
|
||||
/// <summary>
|
||||
/// 406 not acceptable
|
||||
/// </summary>
|
||||
[Description("Not Acceptable")]
|
||||
NotAcceptable = 406,
|
||||
/// <summary>
|
||||
/// 407 proxy authentication required
|
||||
/// </summary>
|
||||
[Description("Proxy Authentication Required")]
|
||||
ProxyAuthenticationRequred = 407,
|
||||
/// <summary>
|
||||
/// 408 request time-out
|
||||
/// </summary>
|
||||
[Description("Request Time-Out")]
|
||||
RequestTimeOut = 408,
|
||||
|
||||
/// <summary>
|
||||
/// 410 gone
|
||||
/// </summary>
|
||||
Gone = 410,
|
||||
/// <summary>
|
||||
/// 411 length required
|
||||
/// </summary>
|
||||
[Description("Length Required")]
|
||||
LengthRequired = 411,
|
||||
/// <summary>
|
||||
/// 412 precondition failed
|
||||
/// </summary>
|
||||
[Description("Precondition Failed")]
|
||||
PreconditionFailed = 412,
|
||||
/// <summary>
|
||||
/// 413 request entity too large
|
||||
/// </summary>
|
||||
[Description("Request Entity Too Large")]
|
||||
RequestEntityTooLarge = 413,
|
||||
/// <summary>
|
||||
/// 414 request URI too large
|
||||
/// </summary>
|
||||
[Description("Request URI Too Large")]
|
||||
RequestUriTooLarge = 414,
|
||||
/// <summary>
|
||||
/// 415 unsupported media type
|
||||
/// </summary>
|
||||
[Description("Unsupported Media Type")]
|
||||
UnsupportedMediaType = 415,
|
||||
|
||||
/// <summary>
|
||||
/// 451 parameter not understood
|
||||
/// </summary>
|
||||
[Description("Parameter Not Understood")]
|
||||
ParameterNotUnderstood = 451,
|
||||
/// <summary>
|
||||
/// 452 conference not found
|
||||
/// </summary>
|
||||
[Description("Conference Not Found")]
|
||||
ConferenceNotFound = 452,
|
||||
/// <summary>
|
||||
/// 453 not enough bandwidth
|
||||
/// </summary>
|
||||
[Description("Not Enough Bandwidth")]
|
||||
NotEnoughBandwidth = 453,
|
||||
/// <summary>
|
||||
/// 454 session not found
|
||||
/// </summary>
|
||||
[Description("Session Not Found")]
|
||||
SessionNotFound = 454,
|
||||
/// <summary>
|
||||
/// 455 method not valid in this state
|
||||
/// </summary>
|
||||
[Description("Method Not Valid In This State")]
|
||||
MethodNotValidInThisState = 455,
|
||||
/// <summary>
|
||||
/// 456 header field not valid for this resource
|
||||
/// </summary>
|
||||
[Description("Header Field Not Valid For This Resource")]
|
||||
HeaderFieldNotValidForThisResource = 456,
|
||||
/// <summary>
|
||||
/// 457 invalid range
|
||||
/// </summary>
|
||||
[Description("Invalid Range")]
|
||||
InvalidRange = 457,
|
||||
/// <summary>
|
||||
/// 458 parameter is read-only
|
||||
/// </summary>
|
||||
[Description("Parameter Is Read-Only")]
|
||||
ParameterIsReadOnly = 458,
|
||||
/// <summary>
|
||||
/// 459 aggregate operation not allowed
|
||||
/// </summary>
|
||||
[Description("Aggregate Operation Not Allowed")]
|
||||
AggregateOperationNotAllowed = 459,
|
||||
/// <summary>
|
||||
/// 460 only aggregate operation allowed
|
||||
/// </summary>
|
||||
[Description("Only Aggregate Operation Allowed")]
|
||||
OnlyAggregateOperationAllowed = 460,
|
||||
/// <summary>
|
||||
/// 461 unsupported transport
|
||||
/// </summary>
|
||||
[Description("Unsupported Transport")]
|
||||
UnsupportedTransport = 461,
|
||||
/// <summary>
|
||||
/// 462 destination unreachable
|
||||
/// </summary>
|
||||
[Description("Destination Unreachable")]
|
||||
DestinationUnreachable = 462,
|
||||
|
||||
/// <summary>
|
||||
/// 500 internal server error
|
||||
/// </summary>
|
||||
[Description("Internal Server Error")]
|
||||
InternalServerError = 500,
|
||||
/// <summary>
|
||||
/// 501 not implemented
|
||||
/// </summary>
|
||||
[Description("Not Implemented")]
|
||||
NotImplemented = 501,
|
||||
/// <summary>
|
||||
/// 502 bad gateway
|
||||
/// </summary>
|
||||
[Description("Bad Gateway")]
|
||||
BadGateway = 502,
|
||||
/// <summary>
|
||||
/// 503 service unavailable
|
||||
/// </summary>
|
||||
[Description("Service Unavailable")]
|
||||
ServiceUnavailable = 503,
|
||||
/// <summary>
|
||||
/// 504 gateway time-out
|
||||
/// </summary>
|
||||
[Description("Gateway Time-Out")]
|
||||
GatewayTimeOut = 504,
|
||||
/// <summary>
|
||||
/// 505 RTSP version not supported
|
||||
/// </summary>
|
||||
[Description("RTSP Version Not Supported")]
|
||||
RtspVersionNotSupported = 505,
|
||||
|
||||
/// <summary>
|
||||
/// 551 option not supported
|
||||
/// </summary>
|
||||
[Description("Option Not Supported")]
|
||||
OptionNotSupported = 551
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ using MediaBrowser.Model.LiveTv;
|
|||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
|
||||
{
|
||||
|
@ -171,58 +172,86 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
|
|||
|
||||
public async Task<SatIpTunerHostInfo> GetInfo(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new SatIpTunerHostInfo
|
||||
Uri locationUri = new Uri(url);
|
||||
string devicetype = "";
|
||||
string friendlyname = "";
|
||||
string uniquedevicename = "";
|
||||
string manufacturer = "";
|
||||
string manufacturerurl = "";
|
||||
string modelname = "";
|
||||
string modeldescription = "";
|
||||
string modelnumber = "";
|
||||
string modelurl = "";
|
||||
string serialnumber = "";
|
||||
string presentationurl = "";
|
||||
string capabilities = "";
|
||||
string m3u = "";
|
||||
var document = XDocument.Load(locationUri.AbsoluteUri);
|
||||
var xnm = new XmlNamespaceManager(new NameTable());
|
||||
XNamespace n1 = "urn:ses-com:satip";
|
||||
XNamespace n0 = "urn:schemas-upnp-org:device-1-0";
|
||||
xnm.AddNamespace("root", n0.NamespaceName);
|
||||
xnm.AddNamespace("satip:", n1.NamespaceName);
|
||||
if (document.Root != null)
|
||||
{
|
||||
Url = url,
|
||||
IsEnabled = true,
|
||||
Type = SatIpHost.DeviceType,
|
||||
Tuners = 1,
|
||||
TunersAvailable = 1
|
||||
};
|
||||
|
||||
using (var stream = await _httpClient.Get(url, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
var deviceElement = document.Root.Element(n0 + "device");
|
||||
if (deviceElement != null)
|
||||
{
|
||||
// Use XmlReader for best performance
|
||||
using (var reader = XmlReader.Create(streamReader))
|
||||
{
|
||||
reader.MoveToContent();
|
||||
|
||||
// Loop through each element
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "device":
|
||||
using (var subtree = reader.ReadSubtree())
|
||||
{
|
||||
FillFromDeviceNode(result, subtree);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var devicetypeElement = deviceElement.Element(n0 + "deviceType");
|
||||
if (devicetypeElement != null)
|
||||
devicetype = devicetypeElement.Value;
|
||||
var friendlynameElement = deviceElement.Element(n0 + "friendlyName");
|
||||
if (friendlynameElement != null)
|
||||
friendlyname = friendlynameElement.Value;
|
||||
var manufactureElement = deviceElement.Element(n0 + "manufacturer");
|
||||
if (manufactureElement != null)
|
||||
manufacturer = manufactureElement.Value;
|
||||
var manufactureurlElement = deviceElement.Element(n0 + "manufacturerURL");
|
||||
if (manufactureurlElement != null)
|
||||
manufacturerurl = manufactureurlElement.Value;
|
||||
var modeldescriptionElement = deviceElement.Element(n0 + "modelDescription");
|
||||
if (modeldescriptionElement != null)
|
||||
modeldescription = modeldescriptionElement.Value;
|
||||
var modelnameElement = deviceElement.Element(n0 + "modelName");
|
||||
if (modelnameElement != null)
|
||||
modelname = modelnameElement.Value;
|
||||
var modelnumberElement = deviceElement.Element(n0 + "modelNumber");
|
||||
if (modelnumberElement != null)
|
||||
modelnumber = modelnumberElement.Value;
|
||||
var modelurlElement = deviceElement.Element(n0 + "modelURL");
|
||||
if (modelurlElement != null)
|
||||
modelurl = modelurlElement.Value;
|
||||
var serialnumberElement = deviceElement.Element(n0 + "serialNumber");
|
||||
if (serialnumberElement != null)
|
||||
serialnumber = serialnumberElement.Value;
|
||||
var uniquedevicenameElement = deviceElement.Element(n0 + "UDN");
|
||||
if (uniquedevicenameElement != null) uniquedevicename = uniquedevicenameElement.Value;
|
||||
var presentationUrlElement = deviceElement.Element(n0 + "presentationURL");
|
||||
if (presentationUrlElement != null) presentationurl = presentationUrlElement.Value;
|
||||
var capabilitiesElement = deviceElement.Element(n1 + "X_SATIPCAP");
|
||||
if (capabilitiesElement != null) capabilities = capabilitiesElement.Value;
|
||||
var m3uElement = deviceElement.Element(n1 + "X_SATIPM3U");
|
||||
if (m3uElement != null) m3u = m3uElement.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result.DeviceId))
|
||||
var result = new SatIpTunerHostInfo
|
||||
{
|
||||
Url = url,
|
||||
Id = uniquedevicename,
|
||||
IsEnabled = true,
|
||||
Type = SatIpHost.DeviceType,
|
||||
Tuners = 1,
|
||||
TunersAvailable = 1,
|
||||
M3UUrl = m3u
|
||||
};
|
||||
|
||||
result.FriendlyName = friendlyname;
|
||||
if (string.IsNullOrWhiteSpace(result.Id))
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Device hasn't implemented an m3u list
|
||||
if (string.IsNullOrWhiteSpace(result.M3UUrl))
|
||||
{
|
||||
result.IsEnabled = false;
|
||||
}
|
||||
|
||||
else if (!result.M3UUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var fullM3uUrl = url.Substring(0, url.LastIndexOf('/'));
|
||||
|
@ -233,66 +262,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void FillFromDeviceNode(SatIpTunerHostInfo info, XmlReader reader)
|
||||
{
|
||||
reader.MoveToContent();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
switch (reader.LocalName)
|
||||
{
|
||||
case "UDN":
|
||||
{
|
||||
info.DeviceId = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
case "friendlyName":
|
||||
{
|
||||
info.FriendlyName = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
case "satip:X_SATIPCAP":
|
||||
case "X_SATIPCAP":
|
||||
{
|
||||
// <satip:X_SATIPCAP xmlns:satip="urn:ses-com:satip">DVBS2-2</satip:X_SATIPCAP>
|
||||
var value = reader.ReadElementContentAsString() ?? string.Empty;
|
||||
var parts = value.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
int intValue;
|
||||
if (int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
|
||||
{
|
||||
info.TunersAvailable = intValue;
|
||||
}
|
||||
|
||||
if (int.TryParse(parts[0].Substring(parts[0].Length - 1), NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
|
||||
{
|
||||
info.Tuners = intValue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "satip:X_SATIPM3U":
|
||||
case "X_SATIPM3U":
|
||||
{
|
||||
// <satip:X_SATIPM3U xmlns:satip="urn:ses-com:satip">/channellist.lua?select=m3u</satip:X_SATIPM3U>
|
||||
info.M3UUrl = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SatIpTunerHostInfo : TunerHostInfo
|
||||
|
|
|
@ -40,7 +40,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
|
|||
return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(tuner.M3UUrl, ChannelIdPrefix, tuner.Id, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new List<ChannelInfo>();
|
||||
var channels = await new ChannelScan(Logger).Scan(tuner, cancellationToken).ConfigureAwait(false);
|
||||
return channels;
|
||||
}
|
||||
|
||||
public static string DeviceType
|
||||
|
|
|
@ -62,6 +62,10 @@
|
|||
<Reference Include="MoreLinq">
|
||||
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Open.Nat, Version=2.0.15.0, Culture=neutral, PublicKeyToken=f22a6a4582336c76, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Open.NAT.2.0.15.0\lib\net45\Open.Nat.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Patterns.Logging">
|
||||
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -99,6 +103,7 @@
|
|||
<Reference Include="ServiceStack.Text">
|
||||
<HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="UniversalDetector">
|
||||
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -242,6 +247,12 @@
|
|||
<Compile Include="LiveTv\ProgramImageProvider.cs" />
|
||||
<Compile Include="LiveTv\RecordingImageProvider.cs" />
|
||||
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
|
||||
<Compile Include="LiveTv\TunerHosts\SatIp\ChannelScan.cs" />
|
||||
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspMethod.cs" />
|
||||
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspRequest.cs" />
|
||||
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspResponse.cs" />
|
||||
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspSession.cs" />
|
||||
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspStatusCode.cs" />
|
||||
<Compile Include="LiveTv\TunerHosts\SatIp\SatIpHost.cs" />
|
||||
<Compile Include="LiveTv\TunerHosts\SatIp\SatIpDiscovery.cs" />
|
||||
<Compile Include="Localization\LocalizationManager.cs" />
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
private readonly ILocalizationManager _localization;
|
||||
private readonly ITaskManager _taskManager;
|
||||
|
||||
public const int MigrationVersion = 20;
|
||||
public const int MigrationVersion = 23;
|
||||
public static bool EnableUnavailableMessage = false;
|
||||
|
||||
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager)
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
|
||||
private IDbCommand _updateInheritedRatingCommand;
|
||||
|
||||
private const int LatestSchemaVersion = 56;
|
||||
private const int LatestSchemaVersion = 58;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<package id="MediaBrowser.Naming" version="1.0.0.49" targetFramework="net45" />
|
||||
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
|
||||
<package id="morelinq" version="1.4.0" targetFramework="net45" />
|
||||
<package id="Open.NAT" version="2.0.15.0" targetFramework="net45" />
|
||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
||||
<package id="SocketHttpListener" version="1.0.0.29" targetFramework="net45" />
|
||||
</packages>
|
|
@ -9,6 +9,8 @@ using System.Collections.Generic;
|
|||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using MediaBrowser.Controller.Power;
|
||||
using MediaBrowser.Server.Startup.Common.FFMpeg;
|
||||
using OperatingSystem = MediaBrowser.Server.Startup.Common.OperatingSystem;
|
||||
|
||||
namespace MediaBrowser.Server.Mono.Native
|
||||
{
|
||||
|
@ -209,6 +211,99 @@ namespace MediaBrowser.Server.Mono.Native
|
|||
{
|
||||
return new NullPowerManagement();
|
||||
}
|
||||
|
||||
public FFMpegInstallInfo GetFfmpegInstallInfo()
|
||||
{
|
||||
return GetInfo(Environment);
|
||||
}
|
||||
|
||||
public static FFMpegInstallInfo GetInfo(NativeEnvironment environment)
|
||||
{
|
||||
var info = new FFMpegInstallInfo();
|
||||
|
||||
// Windows builds: http://ffmpeg.zeranoe.com/builds/
|
||||
// Linux builds: http://johnvansickle.com/ffmpeg/
|
||||
// OS X builds: http://ffmpegmac.net/
|
||||
// OS X x64: http://www.evermeet.cx/ffmpeg/
|
||||
|
||||
switch (environment.OperatingSystem)
|
||||
{
|
||||
case OperatingSystem.Bsd:
|
||||
break;
|
||||
case OperatingSystem.Linux:
|
||||
|
||||
info.ArchiveType = "7z";
|
||||
info.Version = "20160215";
|
||||
break;
|
||||
case OperatingSystem.Osx:
|
||||
|
||||
info.ArchiveType = "7z";
|
||||
|
||||
switch (environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
info.Version = "20160124";
|
||||
break;
|
||||
case Architecture.X86:
|
||||
info.Version = "20150110";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
info.DownloadUrls = GetDownloadUrls(environment);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private static string[] GetDownloadUrls(NativeEnvironment environment)
|
||||
{
|
||||
switch (environment.OperatingSystem)
|
||||
{
|
||||
case OperatingSystem.Osx:
|
||||
|
||||
switch (environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z"
|
||||
};
|
||||
case Architecture.X86:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x86-2.5.3.7z"
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case OperatingSystem.Linux:
|
||||
|
||||
switch (environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z"
|
||||
};
|
||||
case Architecture.X86:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z"
|
||||
};
|
||||
case Architecture.Arm:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-arm.7z"
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// No version available
|
||||
return new string[] { };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class NullPowerManagement : IPowerManagement
|
||||
|
|
|
@ -618,7 +618,7 @@ namespace MediaBrowser.Server.Startup.Common
|
|||
/// <returns>Task.</returns>
|
||||
private async Task RegisterMediaEncoder(IProgress<double> progress)
|
||||
{
|
||||
var info = await new FFMpegDownloader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, NativeApp.Environment)
|
||||
var info = await new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, NativeApp.Environment, NativeApp.GetType().Assembly, NativeApp.GetFfmpegInstallInfo())
|
||||
.GetFFMpegInfo(NativeApp.Environment, _startupOptions, progress).ConfigureAwait(false);
|
||||
|
||||
var mediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"),
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
|
||||
namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
||||
{
|
||||
public class FFMpegDownloadInfo
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public string FFMpegFilename { get; set; }
|
||||
public string FFProbeFilename { get; set; }
|
||||
public string ArchiveType { get; set; }
|
||||
public string[] DownloadUrls { get; set; }
|
||||
|
||||
public FFMpegDownloadInfo()
|
||||
{
|
||||
DownloadUrls = new string[] { };
|
||||
Version = "Path";
|
||||
FFMpegFilename = "ffmpeg";
|
||||
FFProbeFilename = "ffprobe";
|
||||
}
|
||||
|
||||
public static FFMpegDownloadInfo GetInfo(NativeEnvironment environment)
|
||||
{
|
||||
var info = new FFMpegDownloadInfo();
|
||||
|
||||
// Windows builds: http://ffmpeg.zeranoe.com/builds/
|
||||
// Linux builds: http://johnvansickle.com/ffmpeg/
|
||||
// OS X builds: http://ffmpegmac.net/
|
||||
// OS X x64: http://www.evermeet.cx/ffmpeg/
|
||||
|
||||
switch (environment.OperatingSystem)
|
||||
{
|
||||
case OperatingSystem.Bsd:
|
||||
break;
|
||||
case OperatingSystem.Linux:
|
||||
|
||||
info.ArchiveType = "7z";
|
||||
info.Version = "20160215";
|
||||
break;
|
||||
case OperatingSystem.Osx:
|
||||
|
||||
info.ArchiveType = "7z";
|
||||
|
||||
switch (environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
info.Version = "20160124";
|
||||
break;
|
||||
case Architecture.X86:
|
||||
info.Version = "20150110";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OperatingSystem.Windows:
|
||||
|
||||
info.FFMpegFilename = "ffmpeg.exe";
|
||||
info.FFProbeFilename = "ffprobe.exe";
|
||||
info.Version = "20160131";
|
||||
info.ArchiveType = "7z";
|
||||
|
||||
switch (environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
break;
|
||||
case Architecture.X86:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
info.DownloadUrls = GetDownloadUrls(environment);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private static string[] GetDownloadUrls(NativeEnvironment environment)
|
||||
{
|
||||
switch (environment.OperatingSystem)
|
||||
{
|
||||
case OperatingSystem.Windows:
|
||||
|
||||
switch (environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160131-win64.7z",
|
||||
"http://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20151109-git-480bad7-win64-static.7z"
|
||||
};
|
||||
case Architecture.X86:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160131-win32.7z",
|
||||
"http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20151109-git-480bad7-win32-static.7z"
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case OperatingSystem.Osx:
|
||||
|
||||
switch (environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z"
|
||||
};
|
||||
case Architecture.X86:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x86-2.5.3.7z"
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case OperatingSystem.Linux:
|
||||
|
||||
switch (environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z"
|
||||
};
|
||||
case Architecture.X86:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z"
|
||||
};
|
||||
case Architecture.Arm:
|
||||
return new[]
|
||||
{
|
||||
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-arm.7z"
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// No version available
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
||||
{
|
||||
public class FFMpegInstallInfo
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public string FFMpegFilename { get; set; }
|
||||
public string FFProbeFilename { get; set; }
|
||||
public string ArchiveType { get; set; }
|
||||
public string[] DownloadUrls { get; set; }
|
||||
public bool IsEmbedded { get; set; }
|
||||
|
||||
public FFMpegInstallInfo()
|
||||
{
|
||||
DownloadUrls = new string[] { };
|
||||
Version = "Path";
|
||||
FFMpegFilename = "ffmpeg";
|
||||
FFProbeFilename = "ffprobe";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -15,7 +16,7 @@ using CommonIO;
|
|||
|
||||
namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
||||
{
|
||||
public class FFMpegDownloader
|
||||
public class FFMpegLoader
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
@ -23,13 +24,15 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
private readonly IZipClient _zipClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly NativeEnvironment _environment;
|
||||
private readonly Assembly _ownerAssembly;
|
||||
private readonly FFMpegInstallInfo _ffmpegInstallInfo;
|
||||
|
||||
private readonly string[] _fontUrls =
|
||||
{
|
||||
"https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z"
|
||||
};
|
||||
|
||||
public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment)
|
||||
public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment, Assembly ownerAssembly, FFMpegInstallInfo ffmpegInstallInfo)
|
||||
{
|
||||
_logger = logger;
|
||||
_appPaths = appPaths;
|
||||
|
@ -37,6 +40,8 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
_zipClient = zipClient;
|
||||
_fileSystem = fileSystem;
|
||||
_environment = environment;
|
||||
_ownerAssembly = ownerAssembly;
|
||||
_ffmpegInstallInfo = ffmpegInstallInfo;
|
||||
}
|
||||
|
||||
public async Task<FFMpegInfo> GetFFMpegInfo(NativeEnvironment environment, StartupOptions options, IProgress<double> progress)
|
||||
|
@ -54,7 +59,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
};
|
||||
}
|
||||
|
||||
var downloadInfo = FFMpegDownloadInfo.GetInfo(environment);
|
||||
var downloadInfo = _ffmpegInstallInfo;
|
||||
|
||||
var version = downloadInfo.Version;
|
||||
|
||||
|
@ -78,11 +83,11 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
Version = version
|
||||
};
|
||||
|
||||
_fileSystem.CreateDirectory(versionedDirectoryPath);
|
||||
_fileSystem.CreateDirectory(versionedDirectoryPath);
|
||||
|
||||
var excludeFromDeletions = new List<string> { versionedDirectoryPath };
|
||||
|
||||
if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath))
|
||||
if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath))
|
||||
{
|
||||
// ffmpeg not present. See if there's an older version we can start with
|
||||
var existingVersion = GetExistingVersion(info, rootEncoderPath);
|
||||
|
@ -106,7 +111,10 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
}
|
||||
}
|
||||
|
||||
await DownloadFonts(versionedDirectoryPath).ConfigureAwait(false);
|
||||
if (_environment.OperatingSystem == OperatingSystem.Windows)
|
||||
{
|
||||
await DownloadFonts(versionedDirectoryPath).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
DeleteOlderFolders(Path.GetDirectoryName(versionedDirectoryPath), excludeFromDeletions);
|
||||
|
||||
|
@ -175,7 +183,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
return null;
|
||||
}
|
||||
|
||||
private async void DownloadFFMpegInBackground(FFMpegDownloadInfo downloadinfo, string directory)
|
||||
private async void DownloadFFMpegInBackground(FFMpegInstallInfo downloadinfo, string directory)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -187,8 +195,24 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
}
|
||||
}
|
||||
|
||||
private async Task DownloadFFMpeg(FFMpegDownloadInfo downloadinfo, string directory, IProgress<double> progress)
|
||||
private async Task DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress<double> progress)
|
||||
{
|
||||
if (downloadinfo.IsEmbedded)
|
||||
{
|
||||
var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(tempFile));
|
||||
|
||||
using (var stream = _ownerAssembly.GetManifestResourceStream(downloadinfo.DownloadUrls[0]))
|
||||
{
|
||||
using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
||||
{
|
||||
await stream.CopyToAsync(fs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
ExtractFFMpeg(downloadinfo, tempFile, directory);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var url in downloadinfo.DownloadUrls)
|
||||
{
|
||||
progress.Report(0);
|
||||
|
@ -216,19 +240,17 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
{
|
||||
throw new ApplicationException("ffmpeg unvailable. Please install it and start the server with two command line arguments: -ffmpeg \"{PATH}\" and -ffprobe \"{PATH}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ApplicationException("Unable to download required components. Please try again later.");
|
||||
}
|
||||
|
||||
throw new ApplicationException("Unable to download required components. Please try again later.");
|
||||
}
|
||||
|
||||
private void ExtractFFMpeg(FFMpegDownloadInfo downloadinfo, string tempFile, string targetFolder)
|
||||
private void ExtractFFMpeg(FFMpegInstallInfo downloadinfo, string tempFile, string targetFolder)
|
||||
{
|
||||
_logger.Info("Extracting ffmpeg from {0}", tempFile);
|
||||
|
||||
var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
|
||||
_fileSystem.CreateDirectory(tempFolder);
|
||||
_fileSystem.CreateDirectory(tempFolder);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -247,7 +269,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
}))
|
||||
{
|
||||
var targetFile = Path.Combine(targetFolder, Path.GetFileName(file));
|
||||
_fileSystem.CopyFile(file, targetFile, true);
|
||||
_fileSystem.CopyFile(file, targetFile, true);
|
||||
SetFilePermissions(targetFile);
|
||||
}
|
||||
}
|
||||
|
@ -268,7 +290,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
}
|
||||
}
|
||||
|
||||
private void ExtractArchive(FFMpegDownloadInfo downloadinfo, string archivePath, string targetPath)
|
||||
private void ExtractArchive(FFMpegInstallInfo downloadinfo, string archivePath, string targetPath)
|
||||
{
|
||||
_logger.Info("Extracting {0} to {1}", archivePath, targetPath);
|
||||
|
||||
|
@ -311,13 +333,13 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
{
|
||||
var fontsDirectory = Path.Combine(targetPath, "fonts");
|
||||
|
||||
_fileSystem.CreateDirectory(fontsDirectory);
|
||||
_fileSystem.CreateDirectory(fontsDirectory);
|
||||
|
||||
const string fontFilename = "ARIALUNI.TTF";
|
||||
|
||||
var fontFile = Path.Combine(fontsDirectory, fontFilename);
|
||||
|
||||
if (_fileSystem.FileExists(fontFile))
|
||||
if (_fileSystem.FileExists(fontFile))
|
||||
{
|
||||
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
|
||||
}
|
||||
|
@ -360,7 +382,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
{
|
||||
try
|
||||
{
|
||||
_fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
|
||||
_fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
|
||||
return;
|
||||
}
|
||||
catch (IOException ex)
|
||||
|
@ -422,7 +444,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
|
|||
const string fontConfigFilename = "fonts.conf";
|
||||
var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
|
||||
|
||||
if (!_fileSystem.FileExists(fontConfigFile))
|
||||
if (!_fileSystem.FileExists(fontConfigFile))
|
||||
{
|
||||
var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
|
||||
|
|
@ -3,6 +3,7 @@ using MediaBrowser.Model.Logging;
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using MediaBrowser.Controller.Power;
|
||||
using MediaBrowser.Server.Startup.Common.FFMpeg;
|
||||
|
||||
namespace MediaBrowser.Server.Startup.Common
|
||||
{
|
||||
|
@ -97,5 +98,7 @@ namespace MediaBrowser.Server.Startup.Common
|
|||
/// </summary>
|
||||
/// <returns>IPowerManagement.</returns>
|
||||
IPowerManagement GetPowerManagement();
|
||||
|
||||
FFMpegInstallInfo GetFfmpegInstallInfo();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,8 +65,8 @@
|
|||
<Compile Include="Browser\BrowserLauncher.cs" />
|
||||
<Compile Include="EntryPoints\KeepServerAwake.cs" />
|
||||
<Compile Include="EntryPoints\StartupWizard.cs" />
|
||||
<Compile Include="FFMpeg\FFMpegDownloader.cs" />
|
||||
<Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
|
||||
<Compile Include="FFMpeg\FFMpegLoader.cs" />
|
||||
<Compile Include="FFMpeg\FFMpegInstallInfo.cs" />
|
||||
<Compile Include="FFMpeg\FFMpegInfo.cs" />
|
||||
<Compile Include="FFMpeg\FFmpegValidator.cs" />
|
||||
<Compile Include="INativeApp.cs" />
|
||||
|
|
|
@ -69,16 +69,10 @@
|
|||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MediaBrowser.IsoMounter">
|
||||
<HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Patterns.Logging, Version=1.0.5494.41209, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="pfmclrapi">
|
||||
<HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\pfmclrapi.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ServiceStack.Interfaces">
|
||||
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -144,6 +138,8 @@
|
|||
<None Include="App.config" />
|
||||
<None Include="app.manifest" />
|
||||
<EmbeddedResource Include="Native\RegisterServer.bat" />
|
||||
<EmbeddedResource Include="ffmpeg\ffmpegx64.7z" />
|
||||
<EmbeddedResource Include="ffmpeg\ffmpegx86.7z" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Reflection;
|
||||
using CommonIO;
|
||||
using MediaBrowser.Controller.Power;
|
||||
using MediaBrowser.Server.Startup.Common.FFMpeg;
|
||||
|
||||
namespace MediaBrowser.ServerApplication.Native
|
||||
{
|
||||
|
@ -30,7 +31,7 @@ namespace MediaBrowser.ServerApplication.Native
|
|||
}
|
||||
|
||||
list.Add(GetType().Assembly);
|
||||
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -124,5 +125,32 @@ namespace MediaBrowser.ServerApplication.Native
|
|||
{
|
||||
return new WindowsPowerManagement(_logger);
|
||||
}
|
||||
|
||||
public FFMpegInstallInfo GetFfmpegInstallInfo()
|
||||
{
|
||||
var info = new FFMpegInstallInfo();
|
||||
|
||||
info.FFMpegFilename = "ffmpeg.exe";
|
||||
info.FFProbeFilename = "ffprobe.exe";
|
||||
info.Version = "20160401";
|
||||
info.ArchiveType = "7z";
|
||||
info.IsEmbedded = true;
|
||||
info.DownloadUrls = GetDownloadUrls();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private string[] GetDownloadUrls()
|
||||
{
|
||||
switch (Environment.SystemArchitecture)
|
||||
{
|
||||
case Architecture.X86_X64:
|
||||
return new[] { "MediaBrowser.ServerApplication.ffmpeg.ffmpegx64.7z" };
|
||||
case Architecture.X86:
|
||||
return new[] { "MediaBrowser.ServerApplication.ffmpeg.ffmpegx86.7z" };
|
||||
}
|
||||
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
9dc10b022537738edce7eb71aa8dd4adbfee2c7b
|
|
@ -0,0 +1 @@
|
|||
00fa1afa35fbd0a7e97ad7956e42ae17f6882f64
|
|
@ -2,7 +2,6 @@
|
|||
<packages>
|
||||
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
|
||||
<package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
|
||||
<package id="MediaBrowser.IsoMounting" version="3.0.69" targetFramework="net45" />
|
||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
||||
<package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
|
||||
</packages>
|
Loading…
Reference in New Issue
Block a user