Merge pull request #1617 from MediaBrowser/dev

Dev
pull/702/head
Luke 9 years ago
commit d2050ac305

@ -485,6 +485,13 @@ namespace MediaBrowser.Api.LiveTv
} }
[Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")]
[Authenticated(AllowBeforeStartupWizard = true)]
public class GetSatChannnelScanResult : TunerHostInfo
{
}
public class LiveTvService : BaseApiService public class LiveTvService : BaseApiService
{ {
private readonly ILiveTvManager _liveTvManager; private readonly ILiveTvManager _liveTvManager;
@ -504,6 +511,13 @@ namespace MediaBrowser.Api.LiveTv
_dtoService = dtoService; _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) public async Task<object> Get(GetLiveTvRegistrationInfo request)
{ {
var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false); var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false);

@ -3,11 +3,11 @@ namespace MediaBrowser.Common.Implementations.Security
{ {
public class MbAdmin public class MbAdmin
{ {
public const string HttpUrl = "http://www.mb3admin.com/admin/"; public const string HttpUrl = "https://www.mb3admin.com/admin/";
/// <summary> /// <summary>
/// Leaving as http for now until we get it squared away /// Leaving as http for now until we get it squared away
/// </summary> /// </summary>
public const string HttpsUrl = "http://www.mb3admin.com/admin/"; public const string HttpsUrl = "https://www.mb3admin.com/admin/";
} }
} }

@ -21,7 +21,7 @@ namespace MediaBrowser.Common.Implementations.Security
public class PluginSecurityManager : ISecurityManager public class PluginSecurityManager : ISecurityManager
{ {
private const string MBValidateUrl = MbAdmin.HttpsUrl + "service/registration/validate"; 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> /// <summary>
/// The _is MB supporter /// The _is MB supporter

@ -255,6 +255,11 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrWhiteSpace(Path)) if (string.IsNullOrWhiteSpace(Path))
{ {
if (SourceType == SourceType.Channel)
{
return LocationType.Remote;
}
return LocationType.Virtual; return LocationType.Virtual;
} }
@ -494,7 +499,19 @@ namespace MediaBrowser.Controller.Entities
{ {
get 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 set
{ {
@ -529,11 +546,6 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
protected virtual string CreateSortName() 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 (Name == null) return null; //some items may not have name filled in properly
if (!EnableAlphaNumericSorting) if (!EnableAlphaNumericSorting)

@ -175,7 +175,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
protected override string CreateSortName() 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; + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name;
} }

@ -79,23 +79,6 @@ namespace MediaBrowser.Controller.Entities
locationType != LocationType.Virtual; locationType != LocationType.Virtual;
} }
[IgnoreDataMember]
public override LocationType LocationType
{
get
{
if (SourceType == SourceType.Channel)
{
if (string.IsNullOrEmpty(Path))
{
return LocationType.Remote;
}
}
return base.LocationType;
}
}
[IgnoreDataMember] [IgnoreDataMember]
public override bool SupportsAddingToPlaylist public override bool SupportsAddingToPlaylist
{ {

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

@ -36,7 +36,7 @@ namespace MediaBrowser.Dlna.Ssdp
private Timer _notificationTimer; private Timer _notificationTimer;
private bool _isDisposed; 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; private readonly IApplicationHost _appHost;
@ -172,9 +172,12 @@ namespace MediaBrowser.Dlna.Ssdp
{ {
get 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) 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(); NotifyAll();
_logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri); _logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri);
}
} }
public void UnregisterNotification(string uuid) public void UnregisterNotification(string uuid)
{ {
List<UpnpDevice> dl; lock (_devices)
if (_devices.TryRemove(uuid, out dl))
{ {
List<UpnpDevice> dl;
foreach (var d in dl.ToList()) if (_devices.TryGetValue(uuid, out dl))
{ {
NotifyDevice(d, "byebye", true); _devices.Remove(uuid);
} foreach (var d in dl.ToList())
{
NotifyDevice(d, "byebye", true);
}
_logger.Debug("Unregistered mount {0}", uuid); _logger.Debug("Unregistered mount {0}", uuid);
}
} }
} }

@ -37,6 +37,10 @@ namespace MediaBrowser.Model.LiveTv
public string FriendlyName { get; set; } public string FriendlyName { get; set; }
public int Tuners { get; set; } public int Tuners { get; set; }
public string DiseqC { 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; } public int DataVersion { get; set; }

@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using Mono.Nat;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -11,6 +10,9 @@ using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
using MediaBrowser.Common.Threading; using MediaBrowser.Common.Threading;
using Open.Nat;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.EntryPoints namespace MediaBrowser.Server.Implementations.EntryPoints
{ {
@ -20,9 +22,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ISsdpHandler _ssdp; private readonly ISsdpHandler _ssdp;
private CancellationTokenSource _currentCancellationTokenSource;
private PeriodicTimer _timer; private TimeSpan _interval = TimeSpan.FromHours(1);
private bool _isStarted;
public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp) public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
{ {
@ -30,225 +31,97 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_appHost = appHost; _appHost = appHost;
_config = config; _config = config;
_ssdp = ssdp; _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() public void Run()
{ {
//NatUtility.Logger = new LogWriter(_logger); Discover();
if (_config.Configuration.EnableUPnP)
{
Start();
}
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
} }
private void Start() private async void Discover()
{ {
_logger.Debug("Starting NAT discovery"); if (!_config.Configuration.EnableUPnP)
NatUtility.EnabledProtocols = new List<NatProtocol>
{ {
NatProtocol.Pmp return;
}; }
NatUtility.DeviceFound += NatUtility_DeviceFound;
// Mono.Nat does never rise this event. The event is there however it is useless.
// You could remove it with no risk.
NatUtility.DeviceLost += NatUtility_DeviceLost;
// it is hard to say what one should do when an unhandled exception is raised
// because there isn't anything one can do about it. Probably save a log or ignored it.
NatUtility.UnhandledException += NatUtility_UnhandledException;
NatUtility.StartDiscovery();
_timer = new PeriodicTimer(s => _createdRules = new List<string>(), null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
_ssdp.MessageReceived += _ssdp_MessageReceived;
_lastConfigIdentifier = GetConfigIdentifier();
_isStarted = true; var discoverer = new NatDiscoverer();
}
void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e) var cancellationTokenSource = new CancellationTokenSource(10000);
{ _currentCancellationTokenSource = cancellationTokenSource;
var endpoint = e.EndPoint as IPEndPoint;
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) await CreateRules(device).ConfigureAwait(false);
{
var ex = e.ExceptionObject as Exception;
if (ex == null)
{
//_logger.Error("Unidentified error reported by Mono.Nat");
} }
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) catch (Exception ex)
{ {
// I think it could be a good idea to log the exception because _logger.ErrorException("Error discovering NAT devices", ex);
// 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);
} }
} finally
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))
{ {
_createdRules.Add(address); _currentCancellationTokenSource = null;
CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
} }
}
private void CreatePortMap(INatDevice device, int privatePort, int publicPort) if (_config.Configuration.EnableUPnP)
{
_logger.Debug("Creating port map on port {0}", privatePort);
device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
{ {
Description = _appHost.Name await Task.Delay(_interval).ConfigureAwait(false);
}); Discover();
}
} }
// As I said before, this method will be never invoked. You can remove it. private async Task CreateRules(NatDevice device)
void NatUtility_DeviceLost(object sender, DeviceEventArgs e)
{ {
var device = e.Device; // On some systems the device discovered event seems to fire repeatedly
_logger.Debug("NAT device lost: {0}", device.LocalAddress.ToString()); // This check will help ensure we're not trying to port map the same device over and over
}
public void Dispose() await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
{ await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
DisposeNat();
} }
private void DisposeNat() private async Task CreatePortMap(NatDevice device, int privatePort, int publicPort)
{ {
_logger.Debug("Stopping NAT discovery"); _logger.Debug("Creating port map on port {0}", privatePort);
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
_ssdp.MessageReceived -= _ssdp_MessageReceived;
try try
{ {
// This is not a significant improvement await device.CreatePortMapAsync(new Mapping(Protocol.Tcp, privatePort, publicPort, _appHost.Name)).ConfigureAwait(false);
NatUtility.StopDiscovery();
NatUtility.DeviceFound -= NatUtility_DeviceFound;
NatUtility.DeviceLost -= NatUtility_DeviceLost;
NatUtility.UnhandledException -= NatUtility_UnhandledException;
} }
// Statements in try-block will no fail because StopDiscovery is a one-line
// method that was no chances to fail.
// public static void StopDiscovery ()
// {
// searching.Reset();
// }
// IMO you could remove the catch-block
catch (Exception ex) catch (Exception ex)
{ {
_logger.ErrorException("Error stopping NAT Discovery", ex); _logger.ErrorException("Error creating port map", ex);
}
finally
{
_isStarted = false;
} }
} }
private class LogWriter : TextWriter public void Dispose()
{ {
private readonly ILogger _logger; DisposeNat();
}
public LogWriter(ILogger logger)
{
_logger = logger;
}
public override Encoding Encoding
{
get { return Encoding.UTF8; }
}
public override void WriteLine(string format, params object[] arg)
{
_logger.Debug(format, arg);
}
public override void WriteLine(string value) private void DisposeNat()
{
if (_currentCancellationTokenSource != null)
{ {
_logger.Debug(value); try
{
_currentCancellationTokenSource.Cancel();
}
catch (Exception ex)
{
_logger.ErrorException("Error calling _currentCancellationTokenSource.Cancel", ex);
}
} }
} }
} }

@ -18,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly ILogger _logger; 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) public UsageReporter(IApplicationHost applicationHost, IHttpClient httpClient, IUserManager userManager, ILogger logger)
{ {

@ -2450,7 +2450,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public List<NameValuePair> GetSatIniMappings() 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(); 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; return null;
} }
var srch = "SatIp.ini.";
var filename = Path.GetFileName(resource);
return new NameValuePair return new NameValuePair
{ {
Name = satType1 + " " + satType2, Name = satType1 + " " + satType2,
Value = satType2 + "|" + Path.GetFileName(resource) Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length)
}; };
} }
} }
} }
public Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
{
return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
}
} }
} }

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using IniParser;
using IniParser.Model;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
public class ChannelScan
{
private readonly ILogger _logger;
public ChannelScan(ILogger logger)
{
_logger = logger;
}
public async Task<List<ChannelInfo>> Scan(TunerHostInfo info, CancellationToken cancellationToken)
{
var ini = info.SourceA.Split('|')[1];
var resource = GetType().Assembly.GetManifestResourceNames().FirstOrDefault(i => i.EndsWith(ini, StringComparison.OrdinalIgnoreCase));
_logger.Info("Opening ini file {0}", resource);
var list = new List<ChannelInfo>();
using (var stream = GetType().Assembly.GetManifestResourceStream(resource))
{
using (var reader = new StreamReader(stream))
{
var parser = new StreamIniDataParser();
var data = parser.ReadData(reader);
var count = GetInt(data, "DVB", "0", 0);
_logger.Info("DVB Count: {0}", count);
var index = 1;
var source = "1";
while (index <= count)
{
cancellationToken.ThrowIfCancellationRequested();
using (var rtspSession = new RtspSession(info.Url, _logger))
{
float percent = count == 0 ? 0 : (float)(index) / count;
percent = Math.Max(percent * 100, 100);
//SetControlPropertyThreadSafe(pgbSearchResult, "Value", (int)percent);
var strArray = data["DVB"][index.ToString(CultureInfo.InvariantCulture)].Split(',');
string tuning;
if (strArray[4] == "S2")
{
tuning = string.Format("src={0}&freq={1}&pol={2}&sr={3}&fec={4}&msys=dvbs2&mtype={5}&plts=on&ro=0.35&pids=0,16,17,18,20", source, strArray[0], strArray[1].ToLower(), strArray[2].ToLower(), strArray[3], strArray[5].ToLower());
}
else
{
tuning = string.Format("src={0}&freq={1}&pol={2}&sr={3}&fec={4}&msys=dvbs&mtype={5}&pids=0,16,17,18,20", source, strArray[0], strArray[1].ToLower(), strArray[2], strArray[3], strArray[5].ToLower());
}
rtspSession.Setup(tuning, "unicast");
rtspSession.Play(string.Empty);
int signallevel;
int signalQuality;
rtspSession.Describe(out signallevel, out signalQuality);
await Task.Delay(500).ConfigureAwait(false);
index++;
}
}
}
}
return list;
}
private int GetInt(IniData data, string s1, string s2, int defaultValue)
{
var value = data[s1][s2];
int numericValue;
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out numericValue))
{
return numericValue;
}
return defaultValue;
}
}
public class SatChannel
{
// TODO: Add properties
}
}

@ -0,0 +1,88 @@
/*
Copyright (C) <2007-2016> <Kay Diefenthal>
SatIp.RtspSample is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SatIp.RtspSample is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
{
/// <summary>
/// Standard RTSP request methods.
/// </summary>
public sealed class RtspMethod
{
public override int GetHashCode()
{
return (_name != null ? _name.GetHashCode() : 0);
}
private readonly string _name;
private static readonly IDictionary<string, RtspMethod> _values = new Dictionary<string, RtspMethod>();
public static readonly RtspMethod Describe = new RtspMethod("DESCRIBE");
public static readonly RtspMethod Announce = new RtspMethod("ANNOUNCE");
public static readonly RtspMethod GetParameter = new RtspMethod("GET_PARAMETER");
public static readonly RtspMethod Options = new RtspMethod("OPTIONS");
public static readonly RtspMethod Pause = new RtspMethod("PAUSE");
public static readonly RtspMethod Play = new RtspMethod("PLAY");
public static readonly RtspMethod Record = new RtspMethod("RECORD");
public static readonly RtspMethod Redirect = new RtspMethod("REDIRECT");
public static readonly RtspMethod Setup = new RtspMethod("SETUP");
public static readonly RtspMethod SetParameter = new RtspMethod("SET_PARAMETER");
public static readonly RtspMethod Teardown = new RtspMethod("TEARDOWN");
private RtspMethod(string name)
{
_name = name;
_values.Add(name, this);
}
public override string ToString()
{
return _name;
}
public override bool Equals(object obj)
{
var method = obj as RtspMethod;
if (method != null && this == method)
{
return true;
}
return false;
}
public static ICollection<RtspMethod> Values
{
get { return _values.Values; }
}
public static explicit operator RtspMethod(string name)
{
RtspMethod value;
if (!_values.TryGetValue(name, out value))
{
return null;
}
return value;
}
public static implicit operator string(RtspMethod method)
{
return method._name;
}
}
}

@ -0,0 +1,140 @@
/*
Copyright (C) <2007-2016> <Kay Diefenthal>
SatIp.RtspSample is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SatIp.RtspSample is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using System.Text;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
{
/// <summary>
/// A simple class that can be used to serialise RTSP requests.
/// </summary>
public class RtspRequest
{
private readonly RtspMethod _method;
private readonly string _uri;
private readonly int _majorVersion;
private readonly int _minorVersion;
private IDictionary<string, string> _headers = new Dictionary<string, string>();
private string _body = string.Empty;
/// <summary>
/// Initialise a new instance of the <see cref="RtspRequest"/> class.
/// </summary>
/// <param name="method">The request method.</param>
/// <param name="uri">The request URI</param>
/// <param name="majorVersion">The major version number.</param>
/// <param name="minorVersion">The minor version number.</param>
public RtspRequest(RtspMethod method, string uri, int majorVersion, int minorVersion)
{
_method = method;
_uri = uri;
_majorVersion = majorVersion;
_minorVersion = minorVersion;
}
/// <summary>
/// Get the request method.
/// </summary>
public RtspMethod Method
{
get
{
return _method;
}
}
/// <summary>
/// Get the request URI.
/// </summary>
public string Uri
{
get
{
return _uri;
}
}
/// <summary>
/// Get the request major version number.
/// </summary>
public int MajorVersion
{
get
{
return _majorVersion;
}
}
/// <summary>
/// Get the request minor version number.
/// </summary>
public int MinorVersion
{
get
{
return _minorVersion;
}
}
/// <summary>
/// Get or set the request headers.
/// </summary>
public IDictionary<string, string> Headers
{
get
{
return _headers;
}
set
{
_headers = value;
}
}
/// <summary>
/// Get or set the request body.
/// </summary>
public string Body
{
get
{
return _body;
}
set
{
_body = value;
}
}
/// <summary>
/// Serialise this request.
/// </summary>
/// <returns>raw request bytes</returns>
public byte[] Serialise()
{
var request = new StringBuilder();
request.AppendFormat("{0} {1} RTSP/{2}.{3}\r\n", _method, _uri, _majorVersion, _minorVersion);
foreach (var header in _headers)
{
request.AppendFormat("{0}: {1}\r\n", header.Key, header.Value);
}
request.AppendFormat("\r\n{0}", _body);
return Encoding.UTF8.GetBytes(request.ToString());
}
}
}

@ -0,0 +1,149 @@
/*
Copyright (C) <2007-2016> <Kay Diefenthal>
SatIp.RtspSample is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SatIp.RtspSample is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
{
/// <summary>
/// A simple class that can be used to deserialise RTSP responses.
/// </summary>
public class RtspResponse
{
private static readonly Regex RegexStatusLine = new Regex(@"RTSP/(\d+)\.(\d+)\s+(\d+)\s+([^.]+?)\r\n(.*)", RegexOptions.Singleline);
private int _majorVersion = 1;
private int _minorVersion;
private RtspStatusCode _statusCode;
private string _reasonPhrase;
private IDictionary<string, string> _headers;
private string _body;
/// <summary>
/// Initialise a new instance of the <see cref="RtspResponse"/> class.
/// </summary>
private RtspResponse()
{
}
/// <summary>
/// Get the response major version number.
/// </summary>
public int MajorVersion
{
get
{
return _majorVersion;
}
}
/// <summary>
/// Get the response minor version number.
/// </summary>
public int MinorVersion
{
get
{
return _minorVersion;
}
}
/// <summary>
/// Get the response status code.
/// </summary>
public RtspStatusCode StatusCode
{
get
{
return _statusCode;
}
}
/// <summary>
/// Get the response reason phrase.
/// </summary>
public string ReasonPhrase
{
get
{
return _reasonPhrase;
}
}
/// <summary>
/// Get the response headers.
/// </summary>
public IDictionary<string, string> Headers
{
get
{
return _headers;
}
}
/// <summary>
/// Get the response body.
/// </summary>
public string Body
{
get
{
return _body;
}
set
{
_body = value;
}
}
/// <summary>
/// Deserialise/parse an RTSP response.
/// </summary>
/// <param name="responseBytes">The raw response bytes.</param>
/// <param name="responseByteCount">The number of valid bytes in the response.</param>
/// <returns>a response object</returns>
public static RtspResponse Deserialise(byte[] responseBytes, int responseByteCount)
{
var response = new RtspResponse();
var responseString = Encoding.UTF8.GetString(responseBytes, 0, responseByteCount);
var m = RegexStatusLine.Match(responseString);
if (m.Success)
{
response._majorVersion = int.Parse(m.Groups[1].Captures[0].Value);
response._minorVersion = int.Parse(m.Groups[2].Captures[0].Value);
response._statusCode = (RtspStatusCode)int.Parse(m.Groups[3].Captures[0].Value);
response._reasonPhrase = m.Groups[4].Captures[0].Value;
responseString = m.Groups[5].Captures[0].Value;
}
var sections = responseString.Split(new[] { "\r\n\r\n" }, StringSplitOptions.None);
response._body = sections[1];
var headers = sections[0].Split(new[] { "\r\n" }, StringSplitOptions.None);
response._headers = new Dictionary<string, string>();
foreach (var headerInfo in headers.Select(header => header.Split(':')))
{
response._headers.Add(headerInfo[0], headerInfo[1].Trim());
}
return response;
}
}
}

@ -0,0 +1,688 @@
/*
Copyright (C) <2007-2016> <Kay Diefenthal>
SatIp.RtspSample is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SatIp.RtspSample is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
{
public class RtspSession : IDisposable
{
#region Private Fields
private static readonly Regex RegexRtspSessionHeader = new Regex(@"\s*([^\s;]+)(;timeout=(\d+))?");
private const int DefaultRtspSessionTimeout = 30; // unit = s
private static readonly Regex RegexDescribeResponseSignalInfo = new Regex(@";tuner=\d+,(\d+),(\d+),(\d+),", RegexOptions.Singleline | RegexOptions.IgnoreCase);
private string _address;
private string _rtspSessionId;
public string RtspSessionId
{
get { return _rtspSessionId; }
set { _rtspSessionId = value; }
}
private int _rtspSessionTimeToLive = 0;
private string _rtspStreamId;
private int _clientRtpPort;
private int _clientRtcpPort;
private int _serverRtpPort;
private int _serverRtcpPort;
private int _rtpPort;
private int _rtcpPort;
private string _rtspStreamUrl;
private string _destination;
private string _source;
private string _transport;
private int _signalLevel;
private int _signalQuality;
private Socket _rtspSocket;
private int _rtspSequenceNum = 1;
private bool _disposed = false;
private readonly ILogger _logger;
#endregion
#region Constructor
public RtspSession(string address, ILogger logger)
{
if (string.IsNullOrWhiteSpace(address))
{
throw new ArgumentNullException("address");
}
_address = address;
_logger = logger;
_logger.Info("Creating RtspSession with url {0}", address);
}
~RtspSession()
{
Dispose(false);
}
#endregion
#region Properties
#region Rtsp
public string RtspStreamId
{
get { return _rtspStreamId; }
set { if (_rtspStreamId != value) { _rtspStreamId = value; OnPropertyChanged("RtspStreamId"); } }
}
public string RtspStreamUrl
{
get { return _rtspStreamUrl; }
set { if (_rtspStreamUrl != value) { _rtspStreamUrl = value; OnPropertyChanged("RtspStreamUrl"); } }
}
public int RtspSessionTimeToLive
{
get
{
if (_rtspSessionTimeToLive == 0)
_rtspSessionTimeToLive = DefaultRtspSessionTimeout;
return _rtspSessionTimeToLive * 1000 - 20;
}
set { if (_rtspSessionTimeToLive != value) { _rtspSessionTimeToLive = value; OnPropertyChanged("RtspSessionTimeToLive"); } }
}
#endregion
#region Rtp Rtcp
/// <summary>
/// The LocalEndPoint Address
/// </summary>
public string Destination
{
get
{
if (string.IsNullOrEmpty(_destination))
{
var result = "";
var host = Dns.GetHostName();
var hostentry = Dns.GetHostEntry(host);
foreach (var ip in hostentry.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork))
{
result = ip.ToString();
}
_destination = result;
}
return _destination;
}
set
{
if (_destination != value)
{
_destination = value;
OnPropertyChanged("Destination");
}
}
}
/// <summary>
/// The RemoteEndPoint Address
/// </summary>
public string Source
{
get { return _source; }
set
{
if (_source != value)
{
_source = value;
OnPropertyChanged("Source");
}
}
}
/// <summary>
/// The Media Data Delivery RemoteEndPoint Port if we use Unicast
/// </summary>
public int ServerRtpPort
{
get
{
return _serverRtpPort;
}
set { if (_serverRtpPort != value) { _serverRtpPort = value; OnPropertyChanged("ServerRtpPort"); } }
}
/// <summary>
/// The Media Metadata Delivery RemoteEndPoint Port if we use Unicast
/// </summary>
public int ServerRtcpPort
{
get { return _serverRtcpPort; }
set { if (_serverRtcpPort != value) { _serverRtcpPort = value; OnPropertyChanged("ServerRtcpPort"); } }
}
/// <summary>
/// The Media Data Delivery LocalEndPoint Port if we use Unicast
/// </summary>
public int ClientRtpPort
{
get { return _clientRtpPort; }
set { if (_clientRtpPort != value) { _clientRtpPort = value; OnPropertyChanged("ClientRtpPort"); } }
}
/// <summary>
/// The Media Metadata Delivery LocalEndPoint Port if we use Unicast
/// </summary>
public int ClientRtcpPort
{
get { return _clientRtcpPort; }
set { if (_clientRtcpPort != value) { _clientRtcpPort = value; OnPropertyChanged("ClientRtcpPort"); } }
}
/// <summary>
/// The Media Data Delivery RemoteEndPoint Port if we use Multicast
/// </summary>
public int RtpPort
{
get { return _rtpPort; }
set { if (_rtpPort != value) { _rtpPort = value; OnPropertyChanged("RtpPort"); } }
}
/// <summary>
/// The Media Meta Delivery RemoteEndPoint Port if we use Multicast
/// </summary>
public int RtcpPort
{
get { return _rtcpPort; }
set { if (_rtcpPort != value) { _rtcpPort = value; OnPropertyChanged("RtcpPort"); } }
}
#endregion
public string Transport
{
get
{
if (string.IsNullOrEmpty(_transport))
{
_transport = "unicast";
}
return _transport;
}
set
{
if (_transport != value)
{
_transport = value;
OnPropertyChanged("Transport");
}
}
}
public int SignalLevel
{
get { return _signalLevel; }
set { if (_signalLevel != value) { _signalLevel = value; OnPropertyChanged("SignalLevel"); } }
}
public int SignalQuality
{
get { return _signalQuality; }
set { if (_signalQuality != value) { _signalQuality = value; OnPropertyChanged("SignalQuality"); } }
}
#endregion
#region Private Methods
private void ProcessSessionHeader(string sessionHeader, string response)
{
if (!string.IsNullOrEmpty(sessionHeader))
{
var m = RegexRtspSessionHeader.Match(sessionHeader);
if (!m.Success)
{
_logger.Error("Failed to tune, RTSP {0} response session header {1} format not recognised", response, sessionHeader);
}
_rtspSessionId = m.Groups[1].Captures[0].Value;
_rtspSessionTimeToLive = m.Groups[3].Captures.Count == 1 ? int.Parse(m.Groups[3].Captures[0].Value) : DefaultRtspSessionTimeout;
}
}
private void ProcessTransportHeader(string transportHeader)
{
if (!string.IsNullOrEmpty(transportHeader))
{
var transports = transportHeader.Split(',');
foreach (var transport in transports)
{
if (transport.Trim().StartsWith("RTP/AVP"))
{
var sections = transport.Split(';');
foreach (var section in sections)
{
var parts = section.Split('=');
if (parts[0].Equals("server_port"))
{
var ports = parts[1].Split('-');
_serverRtpPort = int.Parse(ports[0]);
_serverRtcpPort = int.Parse(ports[1]);
}
else if (parts[0].Equals("destination"))
{
_destination = parts[1];
}
else if (parts[0].Equals("port"))
{
var ports = parts[1].Split('-');
_rtpPort = int.Parse(ports[0]);
_rtcpPort = int.Parse(ports[1]);
}
else if (parts[0].Equals("ttl"))
{
_rtspSessionTimeToLive = int.Parse(parts[1]);
}
else if (parts[0].Equals("source"))
{
_source = parts[1];
}
else if (parts[0].Equals("client_port"))
{
var ports = parts[1].Split('-');
var rtp = int.Parse(ports[0]);
var rtcp = int.Parse(ports[1]);
//if (!rtp.Equals(_rtpPort))
//{
// Logger.Error("SAT>IP base: server specified RTP client port {0} instead of {1}", rtp, _rtpPort);
//}
//if (!rtcp.Equals(_rtcpPort))
//{
// Logger.Error("SAT>IP base: server specified RTCP client port {0} instead of {1}", rtcp, _rtcpPort);
//}
_rtpPort = rtp;
_rtcpPort = rtcp;
}
}
}
}
}
}
private void Connect()
{
_rtspSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var ip = IPAddress.Parse(_address);
var rtspEndpoint = new IPEndPoint(ip, 554);
_rtspSocket.Connect(rtspEndpoint);
}
private void Disconnect()
{
if (_rtspSocket != null && _rtspSocket.Connected)
{
_rtspSocket.Shutdown(SocketShutdown.Both);
_rtspSocket.Close();
}
}
private void SendRequest(RtspRequest request)
{
if (_rtspSocket == null)
{
Connect();
}
try
{
request.Headers.Add("CSeq", _rtspSequenceNum.ToString());
_rtspSequenceNum++;
byte[] requestBytes = request.Serialise();
if (_rtspSocket != null)
{
var requestBytesCount = _rtspSocket.Send(requestBytes, requestBytes.Length, SocketFlags.None);
if (requestBytesCount < 1)
{
}
}
}
catch (Exception e)
{
_logger.Error(e.Message);
}
}
private void ReceiveResponse(out RtspResponse response)
{
response = null;
var responseBytesCount = 0;
byte[] responseBytes = new byte[1024];
try
{
responseBytesCount = _rtspSocket.Receive(responseBytes, responseBytes.Length, SocketFlags.None);
response = RtspResponse.Deserialise(responseBytes, responseBytesCount);
string contentLengthString;
int contentLength = 0;
if (response.Headers.TryGetValue("Content-Length", out contentLengthString))
{
contentLength = int.Parse(contentLengthString);
if ((string.IsNullOrEmpty(response.Body) && contentLength > 0) || response.Body.Length < contentLength)
{
if (response.Body == null)
{
response.Body = string.Empty;
}
while (responseBytesCount > 0 && response.Body.Length < contentLength)
{
responseBytesCount = _rtspSocket.Receive(responseBytes, responseBytes.Length, SocketFlags.None);
response.Body += System.Text.Encoding.UTF8.GetString(responseBytes, 0, responseBytesCount);
}
}
}
}
catch (SocketException)
{
}
}
#endregion
#region Public Methods
public RtspStatusCode Setup(string query, string transporttype)
{
RtspRequest request;
RtspResponse response;
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
if ((_rtspSocket == null))
{
Connect();
}
if (string.IsNullOrEmpty(_rtspSessionId))
{
request = new RtspRequest(RtspMethod.Setup, string.Format("rtsp://{0}:{1}/?{2}", _address, 554, query), 1, 0);
switch (transporttype)
{
case "multicast":
request.Headers.Add("Transport", string.Format("RTP/AVP;multicast"));
break;
case "unicast":
var activeTcpConnections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections();
var usedPorts = new HashSet<int>();
foreach (var connection in activeTcpConnections)
{
usedPorts.Add(connection.LocalEndPoint.Port);
}
for (var port = 40000; port <= 65534; port += 2)
{
if (!usedPorts.Contains(port) && !usedPorts.Contains(port + 1))
{
_clientRtpPort = port;
_clientRtcpPort = port + 1;
break;
}
}
request.Headers.Add("Transport", string.Format("RTP/AVP;unicast;client_port={0}-{1}", _clientRtpPort, _clientRtcpPort));
break;
}
}
else
{
request = new RtspRequest(RtspMethod.Setup, string.Format("rtsp://{0}:{1}/?{2}", _address, 554, query), 1, 0);
switch (transporttype)
{
case "multicast":
request.Headers.Add("Transport", string.Format("RTP/AVP;multicast"));
break;
case "unicast":
request.Headers.Add("Transport", string.Format("RTP/AVP;unicast;client_port={0}-{1}", _clientRtpPort, _clientRtcpPort));
break;
}
}
SendRequest(request);
ReceiveResponse(out response);
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
//{
// Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
//}
if (!response.Headers.TryGetValue("com.ses.streamID", out _rtspStreamId))
{
_logger.Error(string.Format("Failed to tune, not able to locate Stream ID header in RTSP SETUP response"));
}
string sessionHeader;
if (!response.Headers.TryGetValue("Session", out sessionHeader))
{
_logger.Error(string.Format("Failed to tune, not able to locate Session header in RTSP SETUP response"));
}
ProcessSessionHeader(sessionHeader, "Setup");
string transportHeader;
if (!response.Headers.TryGetValue("Transport", out transportHeader))
{
_logger.Error(string.Format("Failed to tune, not able to locate Transport header in RTSP SETUP response"));
}
ProcessTransportHeader(transportHeader);
return response.StatusCode;
}
public RtspStatusCode Play(string query)
{
if ((_rtspSocket == null))
{
Connect();
}
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
RtspResponse response;
string data;
if (string.IsNullOrEmpty(query))
{
data = string.Format("rtsp://{0}:{1}/stream={2}", _address,
554, _rtspStreamId);
}
else
{
data = string.Format("rtsp://{0}:{1}/stream={2}?{3}", _address,
554, _rtspStreamId, query);
}
var request = new RtspRequest(RtspMethod.Play, data, 1, 0);
request.Headers.Add("Session", _rtspSessionId);
SendRequest(request);
ReceiveResponse(out response);
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
//{
// Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
//}
//Logger.Info("RtspSession-Play : \r\n {0}", response);
string sessionHeader;
if (!response.Headers.TryGetValue("Session", out sessionHeader))
{
_logger.Error(string.Format("Failed to tune, not able to locate Session header in RTSP Play response"));
}
ProcessSessionHeader(sessionHeader, "Play");
string rtpinfoHeader;
if (!response.Headers.TryGetValue("RTP-Info", out rtpinfoHeader))
{
_logger.Error(string.Format("Failed to tune, not able to locate Rtp-Info header in RTSP Play response"));
}
return response.StatusCode;
}
public RtspStatusCode Options()
{
if ((_rtspSocket == null))
{
Connect();
}
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
RtspRequest request;
RtspResponse response;
if (string.IsNullOrEmpty(_rtspSessionId))
{
request = new RtspRequest(RtspMethod.Options, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
}
else
{
request = new RtspRequest(RtspMethod.Options, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
request.Headers.Add("Session", _rtspSessionId);
}
SendRequest(request);
ReceiveResponse(out response);
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
//{
// Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
//}
//Logger.Info("RtspSession-Options : \r\n {0}", response);
string sessionHeader;
if (!response.Headers.TryGetValue("Session", out sessionHeader))
{
_logger.Error(string.Format("Failed to tune, not able to locate session header in RTSP Options response"));
}
ProcessSessionHeader(sessionHeader, "Options");
string optionsHeader;
if (!response.Headers.TryGetValue("Public", out optionsHeader))
{
_logger.Error(string.Format("Failed to tune, not able to Options header in RTSP Options response"));
}
return response.StatusCode;
}
public RtspStatusCode Describe(out int level, out int quality)
{
if ((_rtspSocket == null))
{
Connect();
}
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
RtspRequest request;
RtspResponse response;
level = 0;
quality = 0;
if (string.IsNullOrEmpty(_rtspSessionId))
{
request = new RtspRequest(RtspMethod.Describe, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
request.Headers.Add("Accept", "application/sdp");
}
else
{
request = new RtspRequest(RtspMethod.Describe, string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId), 1, 0);
request.Headers.Add("Accept", "application/sdp");
request.Headers.Add("Session", _rtspSessionId);
}
SendRequest(request);
ReceiveResponse(out response);
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
//{
// Logger.Error("Failed to tune, non-OK RTSP Describe status code {0} {1}", response.StatusCode, response.ReasonPhrase);
//}
//Logger.Info("RtspSession-Describe : \r\n {0}", response);
string sessionHeader;
if (!response.Headers.TryGetValue("Session", out sessionHeader))
{
_logger.Error(string.Format("Failed to tune, not able to locate session header in RTSP Describe response"));
}
ProcessSessionHeader(sessionHeader, "Describe");
var m = RegexDescribeResponseSignalInfo.Match(response.Body);
if (m.Success)
{
//isSignalLocked = m.Groups[2].Captures[0].Value.Equals("1");
level = int.Parse(m.Groups[1].Captures[0].Value) * 100 / 255; // level: 0..255 => 0..100
quality = int.Parse(m.Groups[3].Captures[0].Value) * 100 / 15; // quality: 0..15 => 0..100
}
/*
v=0
o=- 1378633020884883 1 IN IP4 192.168.2.108
s=SatIPServer:1 4
t=0 0
a=tool:idl4k
m=video 52780 RTP/AVP 33
c=IN IP4 0.0.0.0
b=AS:5000
a=control:stream=4
a=fmtp:33 ver=1.0;tuner=1,0,0,0,12344,h,dvbs2,,off,,22000,34;pids=0,100,101,102,103,106
=sendonly
*/
return response.StatusCode;
}
public RtspStatusCode TearDown()
{
if ((_rtspSocket == null))
{
Connect();
}
//_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
RtspResponse response;
var request = new RtspRequest(RtspMethod.Teardown, string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId), 1, 0);
request.Headers.Add("Session", _rtspSessionId);
SendRequest(request);
ReceiveResponse(out response);
//if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
//{
// Logger.Error("Failed to tune, non-OK RTSP Teardown status code {0} {1}", response.StatusCode, response.ReasonPhrase);
//}
return response.StatusCode;
}
#endregion
#region Public Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Protected Methods
protected void OnPropertyChanged(string name)
{
//var handler = PropertyChanged;
//if (handler != null)
//{
// handler(this, new PropertyChangedEventArgs(name));
//}
}
#endregion
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);//Disconnect();
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
TearDown();
Disconnect();
}
}
_disposed = true;
}
}
}

@ -0,0 +1,251 @@
/*
Copyright (C) <2007-2016> <Kay Diefenthal>
SatIp.RtspSample is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SatIp.RtspSample is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
*/
using System.ComponentModel;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
{
/// <summary>
/// Standard RTSP status codes.
/// </summary>
public enum RtspStatusCode
{
/// <summary>
/// 100 continue
/// </summary>
Continue = 100,
/// <summary>
/// 200 OK
/// </summary>
[Description("Okay")]
Ok = 200,
/// <summary>
/// 201 created
/// </summary>
Created = 201,
/// <summary>
/// 250 low on storage space
/// </summary>
[Description("Low On Storage Space")]
LowOnStorageSpace = 250,
/// <summary>
/// 300 multiple choices
/// </summary>
[Description("Multiple Choices")]
MultipleChoices = 300,
/// <summary>
/// 301 moved permanently
/// </summary>
[Description("Moved Permanently")]
MovedPermanently = 301,
/// <summary>
/// 302 moved temporarily
/// </summary>
[Description("Moved Temporarily")]
MovedTemporarily = 302,
/// <summary>
/// 303 see other
/// </summary>
[Description("See Other")]
SeeOther = 303,
/// <summary>
/// 304 not modified
/// </summary>
[Description("Not Modified")]
NotModified = 304,
/// <summary>
/// 305 use proxy
/// </summary>
[Description("Use Proxy")]
UseProxy = 305,
/// <summary>
/// 400 bad request
/// </summary>
[Description("Bad Request")]
BadRequest = 400,
/// <summary>
/// 401 unauthorised
/// </summary>
Unauthorised = 401,
/// <summary>
/// 402 payment required
/// </summary>
[Description("Payment Required")]
PaymentRequired = 402,
/// <summary>
/// 403 forbidden
/// </summary>
Forbidden = 403,
/// <summary>
/// 404 not found
/// </summary>
[Description("Not Found")]
NotFound = 404,
/// <summary>
/// 405 method not allowed
/// </summary>
[Description("Method Not Allowed")]
MethodNotAllowed = 405,
/// <summary>
/// 406 not acceptable
/// </summary>
[Description("Not Acceptable")]
NotAcceptable = 406,
/// <summary>
/// 407 proxy authentication required
/// </summary>
[Description("Proxy Authentication Required")]
ProxyAuthenticationRequred = 407,
/// <summary>
/// 408 request time-out
/// </summary>
[Description("Request Time-Out")]
RequestTimeOut = 408,
/// <summary>
/// 410 gone
/// </summary>
Gone = 410,
/// <summary>
/// 411 length required
/// </summary>
[Description("Length Required")]
LengthRequired = 411,
/// <summary>
/// 412 precondition failed
/// </summary>
[Description("Precondition Failed")]
PreconditionFailed = 412,
/// <summary>
/// 413 request entity too large
/// </summary>
[Description("Request Entity Too Large")]
RequestEntityTooLarge = 413,
/// <summary>
/// 414 request URI too large
/// </summary>
[Description("Request URI Too Large")]
RequestUriTooLarge = 414,
/// <summary>
/// 415 unsupported media type
/// </summary>
[Description("Unsupported Media Type")]
UnsupportedMediaType = 415,
/// <summary>
/// 451 parameter not understood
/// </summary>
[Description("Parameter Not Understood")]
ParameterNotUnderstood = 451,
/// <summary>
/// 452 conference not found
/// </summary>
[Description("Conference Not Found")]
ConferenceNotFound = 452,
/// <summary>
/// 453 not enough bandwidth
/// </summary>
[Description("Not Enough Bandwidth")]
NotEnoughBandwidth = 453,
/// <summary>
/// 454 session not found
/// </summary>
[Description("Session Not Found")]
SessionNotFound = 454,
/// <summary>
/// 455 method not valid in this state
/// </summary>
[Description("Method Not Valid In This State")]
MethodNotValidInThisState = 455,
/// <summary>
/// 456 header field not valid for this resource
/// </summary>
[Description("Header Field Not Valid For This Resource")]
HeaderFieldNotValidForThisResource = 456,
/// <summary>
/// 457 invalid range
/// </summary>
[Description("Invalid Range")]
InvalidRange = 457,
/// <summary>
/// 458 parameter is read-only
/// </summary>
[Description("Parameter Is Read-Only")]
ParameterIsReadOnly = 458,
/// <summary>
/// 459 aggregate operation not allowed
/// </summary>
[Description("Aggregate Operation Not Allowed")]
AggregateOperationNotAllowed = 459,
/// <summary>
/// 460 only aggregate operation allowed
/// </summary>
[Description("Only Aggregate Operation Allowed")]
OnlyAggregateOperationAllowed = 460,
/// <summary>
/// 461 unsupported transport
/// </summary>
[Description("Unsupported Transport")]
UnsupportedTransport = 461,
/// <summary>
/// 462 destination unreachable
/// </summary>
[Description("Destination Unreachable")]
DestinationUnreachable = 462,
/// <summary>
/// 500 internal server error
/// </summary>
[Description("Internal Server Error")]
InternalServerError = 500,
/// <summary>
/// 501 not implemented
/// </summary>
[Description("Not Implemented")]
NotImplemented = 501,
/// <summary>
/// 502 bad gateway
/// </summary>
[Description("Bad Gateway")]
BadGateway = 502,
/// <summary>
/// 503 service unavailable
/// </summary>
[Description("Service Unavailable")]
ServiceUnavailable = 503,
/// <summary>
/// 504 gateway time-out
/// </summary>
[Description("Gateway Time-Out")]
GatewayTimeOut = 504,
/// <summary>
/// 505 RTSP version not supported
/// </summary>
[Description("RTSP Version Not Supported")]
RtspVersionNotSupported = 505,
/// <summary>
/// 551 option not supported
/// </summary>
[Description("Option Not Supported")]
OptionNotSupported = 551
}
}

@ -15,6 +15,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using System.Xml.Linq;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp 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) 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 var result = new SatIpTunerHostInfo
{ {
Url = url, Url = url,
Id = uniquedevicename,
IsEnabled = true, IsEnabled = true,
Type = SatIpHost.DeviceType, Type = SatIpHost.DeviceType,
Tuners = 1, Tuners = 1,
TunersAvailable = 1 TunersAvailable = 1,
M3UUrl = m3u
}; };
using (var stream = await _httpClient.Get(url, cancellationToken).ConfigureAwait(false)) result.FriendlyName = friendlyname;
{ if (string.IsNullOrWhiteSpace(result.Id))
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))
{ {
throw new NotImplementedException(); 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)) else if (!result.M3UUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{ {
var fullM3uUrl = url.Substring(0, url.LastIndexOf('/')); var fullM3uUrl = url.Substring(0, url.LastIndexOf('/'));
@ -233,66 +262,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return result; 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 public class SatIpTunerHostInfo : TunerHostInfo

@ -40,7 +40,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(tuner.M3UUrl, ChannelIdPrefix, tuner.Id, cancellationToken).ConfigureAwait(false); return 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 public static string DeviceType

@ -62,6 +62,10 @@
<Reference Include="MoreLinq"> <Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath> <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
</Reference> </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"> <Reference Include="Patterns.Logging">
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath> <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference> </Reference>
@ -99,6 +103,7 @@
<Reference Include="ServiceStack.Text"> <Reference Include="ServiceStack.Text">
<HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath> <HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="UniversalDetector"> <Reference Include="UniversalDetector">
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath> <HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
</Reference> </Reference>
@ -242,6 +247,12 @@
<Compile Include="LiveTv\ProgramImageProvider.cs" /> <Compile Include="LiveTv\ProgramImageProvider.cs" />
<Compile Include="LiveTv\RecordingImageProvider.cs" /> <Compile Include="LiveTv\RecordingImageProvider.cs" />
<Compile Include="LiveTv\RefreshChannelsScheduledTask.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\SatIpHost.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\SatIpDiscovery.cs" /> <Compile Include="LiveTv\TunerHosts\SatIp\SatIpDiscovery.cs" />
<Compile Include="Localization\LocalizationManager.cs" /> <Compile Include="Localization\LocalizationManager.cs" />

@ -32,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
public const int MigrationVersion = 20; public const int MigrationVersion = 23;
public static bool EnableUnavailableMessage = false; public static bool EnableUnavailableMessage = false;
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager) public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager)

@ -79,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _updateInheritedRatingCommand; private IDbCommand _updateInheritedRatingCommand;
private const int LatestSchemaVersion = 56; private const int LatestSchemaVersion = 58;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class. /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.

@ -7,6 +7,7 @@
<package id="MediaBrowser.Naming" version="1.0.0.49" targetFramework="net45" /> <package id="MediaBrowser.Naming" version="1.0.0.49" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" /> <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.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="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SocketHttpListener" version="1.0.0.29" targetFramework="net45" /> <package id="SocketHttpListener" version="1.0.0.29" targetFramework="net45" />
</packages> </packages>

@ -9,6 +9,8 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using MediaBrowser.Controller.Power; using MediaBrowser.Controller.Power;
using MediaBrowser.Server.Startup.Common.FFMpeg;
using OperatingSystem = MediaBrowser.Server.Startup.Common.OperatingSystem;
namespace MediaBrowser.Server.Mono.Native namespace MediaBrowser.Server.Mono.Native
{ {
@ -209,6 +211,99 @@ namespace MediaBrowser.Server.Mono.Native
{ {
return new NullPowerManagement(); 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 public class NullPowerManagement : IPowerManagement

@ -618,7 +618,7 @@ namespace MediaBrowser.Server.Startup.Common
/// <returns>Task.</returns> /// <returns>Task.</returns>
private async Task RegisterMediaEncoder(IProgress<double> progress) 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); .GetFFMpegInfo(NativeApp.Environment, _startupOptions, progress).ConfigureAwait(false);
var mediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), var mediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"),

@ -1,142 +0,0 @@

namespace MediaBrowser.Server.Startup.Common.FFMpeg
{
public class FFMpegDownloadInfo
{
public string Version { get; set; }
public string FFMpegFilename { get; set; }
public string FFProbeFilename { get; set; }
public string ArchiveType { get; set; }
public string[] DownloadUrls { get; set; }
public FFMpegDownloadInfo()
{
DownloadUrls = new string[] { };
Version = "Path";
FFMpegFilename = "ffmpeg";
FFProbeFilename = "ffprobe";
}
public static FFMpegDownloadInfo GetInfo(NativeEnvironment environment)
{
var info = new FFMpegDownloadInfo();
// Windows builds: http://ffmpeg.zeranoe.com/builds/
// Linux builds: http://johnvansickle.com/ffmpeg/
// OS X builds: http://ffmpegmac.net/
// OS X x64: http://www.evermeet.cx/ffmpeg/
switch (environment.OperatingSystem)
{
case OperatingSystem.Bsd:
break;
case OperatingSystem.Linux:
info.ArchiveType = "7z";
info.Version = "20160215";
break;
case OperatingSystem.Osx:
info.ArchiveType = "7z";
switch (environment.SystemArchitecture)
{
case Architecture.X86_X64:
info.Version = "20160124";
break;
case Architecture.X86:
info.Version = "20150110";
break;
}
break;
case OperatingSystem.Windows:
info.FFMpegFilename = "ffmpeg.exe";
info.FFProbeFilename = "ffprobe.exe";
info.Version = "20160131";
info.ArchiveType = "7z";
switch (environment.SystemArchitecture)
{
case Architecture.X86_X64:
break;
case Architecture.X86:
break;
}
break;
}
info.DownloadUrls = GetDownloadUrls(environment);
return info;
}
private static string[] GetDownloadUrls(NativeEnvironment environment)
{
switch (environment.OperatingSystem)
{
case OperatingSystem.Windows:
switch (environment.SystemArchitecture)
{
case Architecture.X86_X64:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160131-win64.7z",
"http://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20151109-git-480bad7-win64-static.7z"
};
case Architecture.X86:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160131-win32.7z",
"http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20151109-git-480bad7-win32-static.7z"
};
}
break;
case OperatingSystem.Osx:
switch (environment.SystemArchitecture)
{
case Architecture.X86_X64:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z"
};
case Architecture.X86:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x86-2.5.3.7z"
};
}
break;
case OperatingSystem.Linux:
switch (environment.SystemArchitecture)
{
case Architecture.X86_X64:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z"
};
case Architecture.X86:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z"
};
case Architecture.Arm:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-arm.7z"
};
}
break;
}
// No version available
return new string[] { };
}
}
}

@ -0,0 +1,21 @@

namespace MediaBrowser.Server.Startup.Common.FFMpeg
{
public class FFMpegInstallInfo
{
public string Version { get; set; }
public string FFMpegFilename { get; set; }
public string FFProbeFilename { get; set; }
public string ArchiveType { get; set; }
public string[] DownloadUrls { get; set; }
public bool IsEmbedded { get; set; }
public FFMpegInstallInfo()
{
DownloadUrls = new string[] { };
Version = "Path";
FFMpegFilename = "ffmpeg";
FFProbeFilename = "ffprobe";
}
}
}

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -15,7 +16,7 @@ using CommonIO;
namespace MediaBrowser.Server.Startup.Common.FFMpeg namespace MediaBrowser.Server.Startup.Common.FFMpeg
{ {
public class FFMpegDownloader public class FFMpegLoader
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
@ -23,13 +24,15 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
private readonly IZipClient _zipClient; private readonly IZipClient _zipClient;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly NativeEnvironment _environment; private readonly NativeEnvironment _environment;
private readonly Assembly _ownerAssembly;
private readonly FFMpegInstallInfo _ffmpegInstallInfo;
private readonly string[] _fontUrls = private readonly string[] _fontUrls =
{ {
"https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z" "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; _logger = logger;
_appPaths = appPaths; _appPaths = appPaths;
@ -37,6 +40,8 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
_zipClient = zipClient; _zipClient = zipClient;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_environment = environment; _environment = environment;
_ownerAssembly = ownerAssembly;
_ffmpegInstallInfo = ffmpegInstallInfo;
} }
public async Task<FFMpegInfo> GetFFMpegInfo(NativeEnvironment environment, StartupOptions options, IProgress<double> progress) 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; var version = downloadInfo.Version;
@ -78,11 +83,11 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
Version = version Version = version
}; };
_fileSystem.CreateDirectory(versionedDirectoryPath); _fileSystem.CreateDirectory(versionedDirectoryPath);
var excludeFromDeletions = new List<string> { 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 // ffmpeg not present. See if there's an older version we can start with
var existingVersion = GetExistingVersion(info, rootEncoderPath); 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); DeleteOlderFolders(Path.GetDirectoryName(versionedDirectoryPath), excludeFromDeletions);
@ -175,7 +183,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
return null; return null;
} }
private async void DownloadFFMpegInBackground(FFMpegDownloadInfo downloadinfo, string directory) private async void DownloadFFMpegInBackground(FFMpegInstallInfo downloadinfo, string directory)
{ {
try 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) foreach (var url in downloadinfo.DownloadUrls)
{ {
progress.Report(0); 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}\""); 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); _logger.Info("Extracting ffmpeg from {0}", tempFile);
var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString()); var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
_fileSystem.CreateDirectory(tempFolder); _fileSystem.CreateDirectory(tempFolder);
try try
{ {
@ -247,7 +269,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
})) }))
{ {
var targetFile = Path.Combine(targetFolder, Path.GetFileName(file)); var targetFile = Path.Combine(targetFolder, Path.GetFileName(file));
_fileSystem.CopyFile(file, targetFile, true); _fileSystem.CopyFile(file, targetFile, true);
SetFilePermissions(targetFile); 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); _logger.Info("Extracting {0} to {1}", archivePath, targetPath);
@ -311,13 +333,13 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
{ {
var fontsDirectory = Path.Combine(targetPath, "fonts"); var fontsDirectory = Path.Combine(targetPath, "fonts");
_fileSystem.CreateDirectory(fontsDirectory); _fileSystem.CreateDirectory(fontsDirectory);
const string fontFilename = "ARIALUNI.TTF"; const string fontFilename = "ARIALUNI.TTF";
var fontFile = Path.Combine(fontsDirectory, fontFilename); var fontFile = Path.Combine(fontsDirectory, fontFilename);
if (_fileSystem.FileExists(fontFile)) if (_fileSystem.FileExists(fontFile))
{ {
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
} }
@ -360,7 +382,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
{ {
try try
{ {
_fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true); _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
return; return;
} }
catch (IOException ex) catch (IOException ex)
@ -422,7 +444,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
const string fontConfigFilename = "fonts.conf"; const string fontConfigFilename = "fonts.conf";
var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); 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); var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);

@ -3,6 +3,7 @@ using MediaBrowser.Model.Logging;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using MediaBrowser.Controller.Power; using MediaBrowser.Controller.Power;
using MediaBrowser.Server.Startup.Common.FFMpeg;
namespace MediaBrowser.Server.Startup.Common namespace MediaBrowser.Server.Startup.Common
{ {
@ -97,5 +98,7 @@ namespace MediaBrowser.Server.Startup.Common
/// </summary> /// </summary>
/// <returns>IPowerManagement.</returns> /// <returns>IPowerManagement.</returns>
IPowerManagement GetPowerManagement(); IPowerManagement GetPowerManagement();
FFMpegInstallInfo GetFfmpegInstallInfo();
} }
} }

@ -65,8 +65,8 @@
<Compile Include="Browser\BrowserLauncher.cs" /> <Compile Include="Browser\BrowserLauncher.cs" />
<Compile Include="EntryPoints\KeepServerAwake.cs" /> <Compile Include="EntryPoints\KeepServerAwake.cs" />
<Compile Include="EntryPoints\StartupWizard.cs" /> <Compile Include="EntryPoints\StartupWizard.cs" />
<Compile Include="FFMpeg\FFMpegDownloader.cs" /> <Compile Include="FFMpeg\FFMpegLoader.cs" />
<Compile Include="FFMpeg\FFMpegDownloadInfo.cs" /> <Compile Include="FFMpeg\FFMpegInstallInfo.cs" />
<Compile Include="FFMpeg\FFMpegInfo.cs" /> <Compile Include="FFMpeg\FFMpegInfo.cs" />
<Compile Include="FFMpeg\FFmpegValidator.cs" /> <Compile Include="FFMpeg\FFmpegValidator.cs" />
<Compile Include="INativeApp.cs" /> <Compile Include="INativeApp.cs" />

@ -69,16 +69,10 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll</HintPath> <HintPath>..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll</HintPath>
</Reference> </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"> <Reference Include="Patterns.Logging, Version=1.0.5494.41209, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath> <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference> </Reference>
<Reference Include="pfmclrapi">
<HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\pfmclrapi.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Interfaces"> <Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath> <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference> </Reference>
@ -144,6 +138,8 @@
<None Include="App.config" /> <None Include="App.config" />
<None Include="app.manifest" /> <None Include="app.manifest" />
<EmbeddedResource Include="Native\RegisterServer.bat" /> <EmbeddedResource Include="Native\RegisterServer.bat" />
<EmbeddedResource Include="ffmpeg\ffmpegx64.7z" />
<EmbeddedResource Include="ffmpeg\ffmpegx86.7z" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using CommonIO; using CommonIO;
using MediaBrowser.Controller.Power; using MediaBrowser.Controller.Power;
using MediaBrowser.Server.Startup.Common.FFMpeg;
namespace MediaBrowser.ServerApplication.Native namespace MediaBrowser.ServerApplication.Native
{ {
@ -124,5 +125,32 @@ namespace MediaBrowser.ServerApplication.Native
{ {
return new WindowsPowerManagement(_logger); return new WindowsPowerManagement(_logger);
} }
public FFMpegInstallInfo GetFfmpegInstallInfo()
{
var info = new FFMpegInstallInfo();
info.FFMpegFilename = "ffmpeg.exe";
info.FFProbeFilename = "ffprobe.exe";
info.Version = "20160401";
info.ArchiveType = "7z";
info.IsEmbedded = true;
info.DownloadUrls = GetDownloadUrls();
return info;
}
private string[] GetDownloadUrls()
{
switch (Environment.SystemArchitecture)
{
case Architecture.X86_X64:
return new[] { "MediaBrowser.ServerApplication.ffmpeg.ffmpegx64.7z" };
case Architecture.X86:
return new[] { "MediaBrowser.ServerApplication.ffmpeg.ffmpegx86.7z" };
}
return new string[] { };
}
} }
} }

@ -0,0 +1 @@
9dc10b022537738edce7eb71aa8dd4adbfee2c7b

@ -0,0 +1 @@
00fa1afa35fbd0a7e97ad7956e42ae17f6882f64

@ -2,7 +2,6 @@
<packages> <packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" /> <package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
<package id="ImageMagickSharp" version="1.0.0.18" 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="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" /> <package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
</packages> </packages>
Loading…
Cancel
Save