Browse Source

Merge pull request #1617 from MediaBrowser/dev

Dev
Luke 9 years ago
parent
commit
d2050ac305
36 changed files with 1839 additions and 497 deletions
  1. 15 1
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  2. 2 2
      MediaBrowser.Common.Implementations/Security/MbAdmin.cs
  3. 1 1
      MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
  4. 18 6
      MediaBrowser.Controller/Entities/BaseItem.cs
  5. 1 1
      MediaBrowser.Controller/Entities/TV/Episode.cs
  6. 0 17
      MediaBrowser.Controller/Entities/Video.cs
  7. 2 0
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  8. 33 14
      MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
  9. 4 0
      MediaBrowser.Model/LiveTv/LiveTvOptions.cs
  10. 49 176
      MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
  11. 1 1
      MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
  12. 10 2
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  13. 105 0
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/ChannelScan.cs
  14. 88 0
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspMethod.cs
  15. 140 0
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspRequest.cs
  16. 149 0
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspResponse.cs
  17. 688 0
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs
  18. 251 0
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspStatusCode.cs
  19. 69 100
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
  20. 2 1
      MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
  21. 11 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  22. 1 1
      MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
  23. 1 1
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  24. 1 0
      MediaBrowser.Server.Implementations/packages.config
  25. 95 0
      MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
  26. 1 1
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  27. 0 142
      MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs
  28. 21 0
      MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs
  29. 42 20
      MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs
  30. 3 0
      MediaBrowser.Server.Startup.Common/INativeApp.cs
  31. 2 2
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  32. 2 6
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  33. 29 1
      MediaBrowser.ServerApplication/Native/WindowsApp.cs
  34. 1 0
      MediaBrowser.ServerApplication/ffmpeg/ffmpegx64.7z.REMOVED.git-id
  35. 1 0
      MediaBrowser.ServerApplication/ffmpeg/ffmpegx86.7z.REMOVED.git-id
  36. 0 1
      MediaBrowser.ServerApplication/packages.config

+ 15 - 1
MediaBrowser.Api/LiveTv/LiveTvService.cs

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

+ 2 - 2
MediaBrowser.Common.Implementations/Security/MbAdmin.cs

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

+ 1 - 1
MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs

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

+ 18 - 6
MediaBrowser.Controller/Entities/BaseItem.cs

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

+ 1 - 1
MediaBrowser.Controller/Entities/TV/Episode.cs

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

+ 0 - 17
MediaBrowser.Controller/Entities/Video.cs

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

+ 2 - 0
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -383,5 +383,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <returns>List&lt;NameValuePair&gt;.</returns>
         List<NameValuePair> GetSatIniMappings();
+
+        Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken);
     }
 }

+ 33 - 14
MediaBrowser.Dlna/Ssdp/SsdpHandler.cs

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

+ 4 - 0
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

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

+ 49 - 176
MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -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,225 +31,97 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             _appHost = appHost;
             _config = config;
             _ssdp = ssdp;
-        }
-
-        private string _lastConfigIdentifier;
-        private string GetConfigIdentifier()
-        {
-            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());
+            _config.ConfigurationUpdated += _config_ConfigurationUpdated;
         }
 
-        void _config_ConfigurationUpdated(object sender, EventArgs e)
+        private 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();
+                return;
+            }
 
-            _isStarted = true;
-        }
+            var discoverer = new NatDiscoverer();
 
-        void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
-        {
-            var endpoint = e.EndPoint as IPEndPoint;
+            var cancellationTokenSource = new CancellationTokenSource(10000);
+            _currentCancellationTokenSource = cancellationTokenSource;
 
-            if (endpoint != null && e.LocalEndPoint != null)
+            try
             {
-                NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp);
-            }
-        }
+                var device = await discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cancellationTokenSource).ConfigureAwait(false);
 
-        void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
-        {
-            var ex = e.ExceptionObject as Exception;
-
-            if (ex == null)
-            {
-                //_logger.Error("Unidentified error reported by Mono.Nat");
+                await CreateRules(device).ConfigureAwait(false);
             }
-            else
+            catch (OperationCanceledException)
             {
-                // Seeing some blank exceptions coming through here
-                //_logger.ErrorException("Error reported by Mono.Nat: ", ex);
-            }
-        }
-
-        void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
-        {
-            try
-            {
-                var device = e.Device;
-                _logger.Debug("NAT device found: {0}", device.LocalAddress.ToString());
 
-                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.  
-
-                // Commenting out because users are reporting problems out of our control
-                //_logger.ErrorException("Error creating port forwarding rules", ex);
+                _logger.ErrorException("Error discovering NAT devices", ex);
             }
-        }
-
-        private List<string> _createdRules = new List<string>();
-        private void CreateRules(INatDevice 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))
+            finally
             {
-                _createdRules.Add(address);
-
-                CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
-                CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
+                _currentCancellationTokenSource = null;
             }
-        }
 
-        private void CreatePortMap(INatDevice device, int privatePort, int publicPort)
-        {
-            _logger.Debug("Creating port map on port {0}", privatePort);
-            device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
+            if (_config.Configuration.EnableUPnP)
             {
-                Description = _appHost.Name
-            });
+                await Task.Delay(_interval).ConfigureAwait(false);
+                Discover();
+            }
         }
 
-        // As I said before, this method will be never invoked. You can remove it.
-        void NatUtility_DeviceLost(object sender, DeviceEventArgs e)
+        private async Task CreateRules(NatDevice device)
         {
-            var device = e.Device;
-            _logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString());
-        }
+            // 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
 
-        public void Dispose()
-        {
-            DisposeNat();
+            await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
+            await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
         }
 
-        private void DisposeNat()
+        private async Task CreatePortMap(NatDevice device, int privatePort, int publicPort)
         {
-            _logger.Debug("Stopping NAT discovery");
-
-            if (_timer != null)
-            {
-                _timer.Dispose();
-                _timer = null;
-            }
-
-            _ssdp.MessageReceived -= _ssdp_MessageReceived;
+            _logger.Debug("Creating port map on port {0}", privatePort);
 
             try
             {
-                // This is not a significant improvement
-                NatUtility.StopDiscovery();
-                NatUtility.DeviceFound -= NatUtility_DeviceFound;
-                NatUtility.DeviceLost -= NatUtility_DeviceLost;
-                NatUtility.UnhandledException -= NatUtility_UnhandledException;
+                await device.CreatePortMapAsync(new Mapping(Protocol.Tcp, privatePort, publicPort, _appHost.Name)).ConfigureAwait(false);
             }
-            // 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;
+                _logger.ErrorException("Error creating port map", ex);
             }
         }
 
-        private class LogWriter : TextWriter
+        public void Dispose()
         {
-            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);
-            }
+            DisposeNat();
+        }
 
-            public override void WriteLine(string value)
+        private void DisposeNat()
+        {
+            if (_currentCancellationTokenSource != null)
             {
-                _logger.Debug(value);
+                try
+                {
+                    _currentCancellationTokenSource.Cancel();
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error calling _currentCancellationTokenSource.Cancel", ex);
+                }
             }
         }
     }

+ 1 - 1
MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs

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

+ 10 - 2
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

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

+ 105 - 0
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/ChannelScan.cs

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

+ 88 - 0
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspMethod.cs

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

+ 140 - 0
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspRequest.cs

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

+ 149 - 0
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspResponse.cs

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

+ 688 - 0
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs

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

+ 251 - 0
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspStatusCode.cs

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

+ 69 - 100
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs

@@ -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)
         {
+            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)
+            {
+                var deviceElement = document.Root.Element(n0 + "device");
+                if (deviceElement != null)
+                {
+                    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;
+                }
+            }
+
             var result = new SatIpTunerHostInfo
             {
                 Url = url,
+                Id = uniquedevicename,
                 IsEnabled = true,
                 Type = SatIpHost.DeviceType,
                 Tuners = 1,
-                TunersAvailable = 1
+                TunersAvailable = 1,
+                M3UUrl = m3u
             };
 
-            using (var stream = await _httpClient.Get(url, cancellationToken).ConfigureAwait(false))
-            {
-                using (var streamReader = new StreamReader(stream))
-                {
-                    // 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;
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (string.IsNullOrWhiteSpace(result.DeviceId))
+            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

+ 2 - 1
MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs

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

+ 11 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

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

+ 1 - 1
MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.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)

+ 1 - 1
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

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

+ 1 - 0
MediaBrowser.Server.Implementations/packages.config

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

+ 95 - 0
MediaBrowser.Server.Mono/Native/BaseMonoApp.cs

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

+ 1 - 1
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

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

+ 0 - 142
MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs

@@ -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[] { };
-        }
-    }
-}

+ 21 - 0
MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs

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

+ 42 - 20
MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs → MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs

@@ -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 - 0
MediaBrowser.Server.Startup.Common/INativeApp.cs

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

+ 2 - 2
MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj

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

+ 2 - 6
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

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

+ 29 - 1
MediaBrowser.ServerApplication/Native/WindowsApp.cs

@@ -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[] { };
+        }
     }
 }

+ 1 - 0
MediaBrowser.ServerApplication/ffmpeg/ffmpegx64.7z.REMOVED.git-id

@@ -0,0 +1 @@
+9dc10b022537738edce7eb71aa8dd4adbfee2c7b

+ 1 - 0
MediaBrowser.ServerApplication/ffmpeg/ffmpegx86.7z.REMOVED.git-id

@@ -0,0 +1 @@
+00fa1afa35fbd0a7e97ad7956e42ae17f6882f64

+ 0 - 1
MediaBrowser.ServerApplication/packages.config

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