Sfoglia il codice sorgente

Revert "Improved polling and Session handling"

This reverts commit 5a1bdc578bf8440cf5ea59555f6ad3ca707768e3.
7illusions 11 anni fa
parent
commit
d6d1c3839a
3 ha cambiato i file con 1489 aggiunte e 1535 eliminazioni
  1. 751 790
      MediaBrowser.Dlna/PlayTo/Device.cs
  2. 481 489
      MediaBrowser.Dlna/PlayTo/DlnaController.cs
  3. 257 256
      MediaBrowser.sln

+ 751 - 790
MediaBrowser.Dlna/PlayTo/Device.cs

@@ -1,790 +1,751 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-
-namespace MediaBrowser.Dlna.PlayTo
-{
-    public sealed class Device : IDisposable
-    {
-        const string ServiceAvtransportId = "urn:upnp-org:serviceId:AVTransport";
-        const string ServiceRenderingId = "urn:upnp-org:serviceId:RenderingControl";
-
-        #region Fields & Properties
-
-        private Timer _timer;
-
-        public DeviceProperties Properties { get; set; }
-
-        private int _muteVol;
-        public bool IsMuted
-        {
-            get
-            {
-                return _muteVol > 0;
-            }
-        }
-
-        private string _currentId = String.Empty;
-        public string CurrentId
-        {
-            get
-            {
-                return _currentId;
-            }
-            set
-            {
-                if (_currentId == value)
-                    return;
-                _currentId = value;
-                NotifyCurrentIdChanged(value);
-            }
-        }
-
-        public int Volume { get; set; }
-
-        public TimeSpan Duration { get; set; }
-
-        private TimeSpan _position = TimeSpan.FromSeconds(0);
-        public TimeSpan Position
-        {
-            get
-            {
-                return _position;
-            }
-            set
-            {
-                _position = value;
-            }
-        }
-
-        private string _transportState = String.Empty;
-        public string TransportState
-        {
-            get
-            {
-                return _transportState;
-            }
-            set
-            {
-                if (_transportState == value)
-                    return;
-
-                _transportState = value;
-
-                if (value == TRANSPORTSTATE.PLAYING || value == TRANSPORTSTATE.STOPPED)
-                    NotifyPlaybackChanged(value == TRANSPORTSTATE.STOPPED);
-            }
-        }
-
-        public bool IsPlaying
-        {
-            get
-            {
-                return TransportState == TRANSPORTSTATE.PLAYING;
-            }
-        }
-
-        public bool IsTransitioning
-        {
-            get
-            {
-                return (TransportState == TRANSPORTSTATE.TRANSITIONING);
-            }
-        }
-
-        public bool IsPaused
-        {
-            get
-            {
-                return TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK;
-            }
-        }
-
-        public bool IsStopped
-        {
-            get
-            {
-                return TransportState == TRANSPORTSTATE.STOPPED;
-            }
-        }
-
-        public DateTime UpdateTime { get; private set; }
-
-        #endregion
-
-        private readonly IHttpClient _httpClient;
-        private readonly ILogger _logger;
-
-        public Device(DeviceProperties deviceProperties, IHttpClient httpClient, ILogger logger)
-        {
-            Properties = deviceProperties;
-            _httpClient = httpClient;
-            _logger = logger;
-        }
-
-        private int GetPlaybackTimerIntervalMs()
-        {
-            return 2000;
-        }
-
-        private int GetInactiveTimerIntervalMs()
-        {
-            return 20000;
-        }
-
-        public void Start()
-        {
-            UpdateTime = DateTime.UtcNow;
-
-            var interval = GetPlaybackTimerIntervalMs();
-
-            _timer = new Timer(TimerCallback, null, interval, interval);
-        }
-
-        private void RestartTimer()
-        {
-            var interval = GetPlaybackTimerIntervalMs();
-
-            _timer.Change(interval, interval);
-        }
-
-
-        /// <summary>
-        /// Restarts the timer in inactive mode.
-        /// </summary>
-        private void RestartTimerInactive()
-        {
-            var interval = GetInactiveTimerIntervalMs();
-
-            _timer.Change(interval, interval);
-        }
-
-        private void StopTimer()
-        {
-            _timer.Change(Timeout.Infinite, Timeout.Infinite);
-        }
-
-        #region Commanding
-
-        public Task<bool> VolumeDown(bool mute = false)
-        {
-            var sendVolume = (Volume - 5) > 0 ? Volume - 5 : 0;
-            if (mute && _muteVol == 0)
-            {
-                sendVolume = 0;
-                _muteVol = Volume;
-            }
-            return SetVolume(sendVolume);
-        }
-
-        public Task<bool> VolumeUp(bool unmute = false)
-        {
-            var sendVolume = (Volume + 5) < 100 ? Volume + 5 : 100;
-            if (unmute && _muteVol > 0)
-                sendVolume = _muteVol;
-            _muteVol = 0;
-            return SetVolume(sendVolume);
-        }
-
-        public Task ToggleMute()
-        {
-            if (_muteVol == 0)
-            {
-                _muteVol = Volume;
-                return SetVolume(0);
-            }
-
-            var tmp = _muteVol;
-            _muteVol = 0;
-            return SetVolume(tmp);
-        }
-
-        public async Task<bool> SetVolume(int value)
-        {
-            var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
-            if (command == null)
-                return true;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
-
-            if (service == null)
-            {
-                throw new InvalidOperationException("Unable to find service");
-            }
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
-                .ConfigureAwait(false);
-            Volume = value;
-            return true;
-        }
-
-        public async Task<TimeSpan> Seek(TimeSpan value)
-        {
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
-            if (command == null)
-                return value;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-
-            if (service == null)
-            {
-                throw new InvalidOperationException("Unable to find service");
-            }
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
-                .ConfigureAwait(false);
-
-            return value;
-        }
-
-        public async Task<bool> SetAvTransport(string url, string header, string metaData)
-        {
-            StopTimer();
-
-            await SetStop().ConfigureAwait(false);
-            CurrentId = "0";
-
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
-            if (command == null)
-                return false;
-
-            var dictionary = new Dictionary<string, string>
-            {
-                {"CurrentURI", url},
-                {"CurrentURIMetaData", CreateDidlMeta(metaData)}
-            };
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-
-            if (service == null)
-            {
-                throw new InvalidOperationException("Unable to find service");
-            }
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, url, dictionary), header)
-                .ConfigureAwait(false);
-
-            if (!IsPlaying)
-            {
-                await Task.Delay(50).ConfigureAwait(false);
-                await SetPlay().ConfigureAwait(false);
-            }
-
-            _lapsCount = SetLapsCountToFull();
-            RestartTimer();
-
-            return true;
-        }
-
-        private string CreateDidlMeta(string value)
-        {
-            if (value == null)
-                return String.Empty;
-
-            var escapedData = value.Replace("<", "&lt;").Replace(">", "&gt;");
-
-            return String.Format(BaseDidl, escapedData.Replace("\r\n", ""));
-        }
-
-        private const string BaseDidl = "&lt;DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"&gt;{0}&lt;/DIDL-Lite&gt;";
-
-        public async Task<bool> SetNextAvTransport(string value, string header, string metaData)
-        {
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI");
-            if (command == null)
-                return false;
-
-            var dictionary = new Dictionary<string, string>
-            {
-                {"NextURI", value},
-                {"NextURIMetaData", CreateDidlMeta(metaData)}
-            };
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-
-            if (service == null)
-            {
-                throw new InvalidOperationException("Unable to find service");
-            }
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, value, dictionary), header)
-                .ConfigureAwait(false);
-
-            await Task.Delay(100).ConfigureAwait(false);
-
-            return true;
-        }
-
-        public async Task<bool> SetPlay()
-        {
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
-            if (command == null)
-                return false;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-
-            if (service == null)
-            {
-                throw new InvalidOperationException("Unable to find service");
-            }
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
-                .ConfigureAwait(false);
-
-            _lapsCount = SetLapsCountToFull();
-            return true;
-        }
-
-        public async Task<bool> SetStop()
-        {
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
-            if (command == null)
-                return false;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
-                .ConfigureAwait(false);
-
-            await Task.Delay(50).ConfigureAwait(false);            
-            return true;
-        }
-
-        public async Task<bool> SetPause()
-        {
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
-            if (command == null)
-                return false;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0))
-                .ConfigureAwait(false);
-
-            await Task.Delay(50).ConfigureAwait(false);
-            TransportState = "PAUSED_PLAYBACK";
-            return true;
-        }
-
-        #endregion
-
-        #region Get data
-        
-        private int GetLapsCount()
-        {
-            // No need to get all data every lap, just every X time. 
-            return 10;
-        }
-
-        int _lapsCount = 0;
-
-        private async void TimerCallback(object sender)
-        {
-            if (_disposed)
-                return;
-
-            StopTimer();
-
-            try
-            {
-                await GetTransportInfo().ConfigureAwait(false);
-                
-                //If we're not playing anything no need to get additional data
-                if (TransportState != TRANSPORTSTATE.STOPPED)
-                {
-                    var hasTrack = await GetPositionInfo().ConfigureAwait(false);
-
-                    // TODO: Why make these requests if hasTrack==false?
-                    // TODO ANSWER Some vendors don't include track in GetPositionInfo, use GetMediaInfo instead.
-                    if (_lapsCount > GetLapsCount())
-                    {
-                        if (!hasTrack)
-                        {
-                            await GetMediaInfo().ConfigureAwait(false);
-                        }
-                        await GetVolume().ConfigureAwait(false);
-                        _lapsCount = 0;
-                    }
-                }               
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error updating device info", ex);
-            }
-
-            _lapsCount++;
-
-            if (_disposed)
-                return;
-
-            //If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
-            if (TransportState != TRANSPORTSTATE.STOPPED)
-                RestartTimer();
-            else
-                RestartTimerInactive();
-        }
-
-        private async Task GetVolume()
-        {
-            var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
-            if (command == null)
-                return;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
-
-            if (service == null)
-            {
-                throw new InvalidOperationException("Unable to find service");
-            }
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
-                .ConfigureAwait(false);
-
-            if (result == null || result.Document == null)
-                return;
-
-            var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
-            var volumeValue = volume == null ? null : volume.Value;
-
-            if (volumeValue == null)
-                return;
-
-            Volume = Int32.Parse(volumeValue);
-
-            //Reset the Mute value if Volume is bigger than zero
-            if (Volume > 0 && _muteVol > 0)
-            {
-                _muteVol = 0;
-            }
-        }
-
-        private async Task GetTransportInfo()
-        {
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
-            if (command == null)
-                return;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-            if (service == null)
-                return;
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
-                .ConfigureAwait(false);
-
-            if (result == null || result.Document == null)
-                return;
-
-            var transportState =
-                result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
-
-            var transportStateValue = transportState == null ? null : transportState.Value;
-
-            if (transportStateValue != null)
-                TransportState = transportStateValue;
-
-            UpdateTime = DateTime.UtcNow;
-        }
-
-        private async Task GetMediaInfo()
-        {
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
-            if (command == null)
-                return;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-
-            if (service == null)
-            {
-                throw new InvalidOperationException("Unable to find service");
-            }
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
-                .ConfigureAwait(false);
-
-            if (result == null || result.Document == null)
-                return;
-
-            var track = result.Document.Descendants("CurrentURIMetaData").Select(i => i.Value).FirstOrDefault();
-
-            if (String.IsNullOrEmpty(track))
-            {
-                CurrentId = "0";
-                return;
-            }
-
-            var uPnpResponse = XElement.Parse(track);
-
-            var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
-
-            var uTrack = uParser.CreateObjectFromXML(new uParserObject
-            {
-                Type = e.GetValue(uPnpNamespaces.uClass),
-                Element = e
-            });
-
-            if (uTrack != null)
-                CurrentId = uTrack.Id;
-        }
-
-        private async Task<bool> GetPositionInfo()
-        {
-            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
-            if (command == null)
-                return true;
-
-            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-
-            if (service == null)
-            {
-                throw new InvalidOperationException("Unable to find service");
-            }
-
-            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
-                .ConfigureAwait(false);
-
-            if (result == null || result.Document == null)
-                return true;
-
-            var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
-            var duration = durationElem == null ? null : durationElem.Value;
-
-            if (duration != null)
-            {
-                Duration = TimeSpan.Parse(duration);
-            }
-
-            var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
-            var position = positionElem == null ? null : positionElem.Value;
-
-            if (position != null)
-            {
-                Position = TimeSpan.Parse(position);
-            }
-
-            var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
-                .FirstOrDefault();
-
-            if (String.IsNullOrEmpty(track))
-            {
-                //If track is null, some vendors do this, use GetMediaInfo instead                    
-                return false;
-            }
-
-            var uPnpResponse = XElement.Parse(track);
-
-            var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
-
-            var uTrack = uBaseObject.Create(e);
-
-            if (uTrack == null)
-                return true;
-
-            CurrentId = uTrack.Id;
-
-            return true;
-        }
-
-        #endregion
-
-        #region From XML
-
-        private async Task GetAVProtocolAsync()
-        {
-            var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
-            if (avService == null)
-                return;
-
-            var url = avService.SCPDURL;
-            if (!url.Contains("/"))
-                url = "/dmr/" + url;
-            if (!url.StartsWith("/"))
-                url = "/" + url;
-
-            var httpClient = new SsdpHttpClient(_httpClient);
-            var document = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
-
-            AvCommands = TransportCommands.Create(document);
-        }
-
-        private async Task GetRenderingProtocolAsync()
-        {
-            var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
-
-            if (avService == null)
-                return;
-            string url = avService.SCPDURL;
-            if (!url.Contains("/"))
-                url = "/dmr/" + url;
-            if (!url.StartsWith("/"))
-                url = "/" + url;
-
-            var httpClient = new SsdpHttpClient(_httpClient);
-            var document = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
-
-            RendererCommands = TransportCommands.Create(document);
-        }
-
-        internal TransportCommands AvCommands
-        {
-            get;
-            set;
-        }
-
-        internal TransportCommands RendererCommands
-        {
-            get;
-            set;
-        }
-
-        public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger)
-        {
-            var ssdpHttpClient = new SsdpHttpClient(httpClient);
-
-            var document = await ssdpHttpClient.GetDataAsync(url).ConfigureAwait(false);
-
-            var deviceProperties = new DeviceProperties();
-
-            var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
-            if (name != null)
-                deviceProperties.Name = name.Value;
-
-            var name2 = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
-            if (name2 != null)
-                deviceProperties.Name = name2.Value;
-
-            var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
-            if (model != null)
-                deviceProperties.ModelName = model.Value;
-
-            var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
-            if (modelNumber != null)
-                deviceProperties.ModelNumber = modelNumber.Value;
-
-            var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
-            if (uuid != null)
-                deviceProperties.UUID = uuid.Value;
-
-            var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
-            if (manufacturer != null)
-                deviceProperties.Manufacturer = manufacturer.Value;
-
-            var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
-            if (manufacturerUrl != null)
-                deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
-
-            var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
-            if (presentationUrl != null)
-                deviceProperties.PresentationUrl = presentationUrl.Value;
-
-
-            deviceProperties.BaseUrl = String.Format("http://{0}:{1}", url.Host, url.Port);
-
-            var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
-
-            if (icon != null)
-            {
-                deviceProperties.Icon = uIcon.Create(icon);
-            }
-
-            var isRenderer = false;
-
-            foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
-            {
-                if (services == null)
-                    return null;
-
-                var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
-
-                if (servicesList == null)
-                    return null;
-
-                foreach (var element in servicesList)
-                {
-                    var service = uService.Create(element);
-
-                    if (service != null)
-                    {
-                        deviceProperties.Services.Add(service);
-                        if (service.ServiceId == ServiceAvtransportId)
-                        {
-                            isRenderer = true;
-                        }
-                    }
-                }
-            }
-
-            if (isRenderer)
-            {
-
-                var device = new Device(deviceProperties, httpClient, logger);
-
-                await device.GetRenderingProtocolAsync().ConfigureAwait(false);
-                await device.GetAVProtocolAsync().ConfigureAwait(false);
-
-                return device;
-            }
-
-            return null;
-        }
-
-        #endregion
-
-        #region Events
-
-        public event EventHandler<TransportStateEventArgs> PlaybackChanged;
-        public event EventHandler<CurrentIdEventArgs> CurrentIdChanged;
-
-        private void NotifyPlaybackChanged(bool value)
-        {
-            if (PlaybackChanged != null)
-            {
-                PlaybackChanged.Invoke(this, new TransportStateEventArgs
-                {
-                    Stopped = IsStopped
-                });
-            }
-        }
-
-        private void NotifyCurrentIdChanged(string value)
-        {
-            if (CurrentIdChanged != null)
-                CurrentIdChanged.Invoke(this, new CurrentIdEventArgs(value));
-        }
-
-        #endregion
-
-        #region IDisposable
-
-        bool _disposed;
-        public void Dispose()
-        {
-            if (!_disposed)
-            {
-                _disposed = true;
-                _timer.Dispose();
-            }
-        }
-
-        #endregion
-
-        public override string ToString()
-        {
-            return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
-        }
-
-        private class TRANSPORTSTATE
-        {
-            public const string STOPPED = "STOPPED";
-            public const string PLAYING = "PLAYING";
-            public const string TRANSITIONING = "TRANSITIONING";
-            public const string PAUSED_PLAYBACK = "PAUSED_PLAYBACK";
-            public const string PAUSED = "PAUSED";
-        }
-       
-    }
-}
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+    public sealed class Device : IDisposable
+    {
+        const string ServiceAvtransportId = "urn:upnp-org:serviceId:AVTransport";
+        const string ServiceRenderingId = "urn:upnp-org:serviceId:RenderingControl";
+
+        #region Fields & Properties
+
+        private Timer _timer;
+
+        public DeviceProperties Properties { get; set; }
+
+        private int _muteVol;
+        public bool IsMuted
+        {
+            get
+            {
+                return _muteVol > 0;
+            }
+        }
+
+        private string _currentId = String.Empty;
+        public string CurrentId
+        {
+            get
+            {
+                return _currentId;
+            }
+            set
+            {
+                if (_currentId == value)
+                    return;
+                _currentId = value;
+                NotifyCurrentIdChanged(value);
+            }
+        }
+
+        public int Volume { get; set; }
+
+        public TimeSpan Duration { get; set; }
+
+        private TimeSpan _position = TimeSpan.FromSeconds(0);
+        public TimeSpan Position
+        {
+            get
+            {
+                return _position;
+            }
+            set
+            {
+                _position = value;
+            }
+        }
+
+        private string _transportState = String.Empty;
+        public string TransportState
+        {
+            get
+            {
+                return _transportState;
+            }
+            set
+            {
+                if (_transportState == value)
+                    return;
+
+                _transportState = value;
+
+                if (value == "PLAYING" || value == "STOPPED")
+                    NotifyPlaybackChanged(value == "STOPPED");
+            }
+        }
+
+        public bool IsPlaying
+        {
+            get
+            {
+                return TransportState == "PLAYING";
+            }
+        }
+
+        public bool IsTransitioning
+        {
+            get
+            {
+                return (TransportState == "TRANSITIONING");
+            }
+        }
+
+        public bool IsPaused
+        {
+            get
+            {
+                return TransportState == "PAUSED" || TransportState == "PAUSED_PLAYBACK";
+            }
+        }
+
+        public bool IsStopped
+        {
+            get
+            {
+                return (TransportState == "STOPPED");
+            }
+        }
+
+        public DateTime UpdateTime { get; private set; }
+
+        #endregion
+
+        private readonly IHttpClient _httpClient;
+        private readonly ILogger _logger;
+
+        public Device(DeviceProperties deviceProperties, IHttpClient httpClient, ILogger logger)
+        {
+            Properties = deviceProperties;
+            _httpClient = httpClient;
+            _logger = logger;
+        }
+
+        private int GetTimerIntervalMs()
+        {
+            return 10000;
+        }
+
+        public void Start()
+        {
+            UpdateTime = DateTime.UtcNow;
+
+            var interval = GetTimerIntervalMs();
+
+            _timer = new Timer(TimerCallback, null, interval, interval);
+        }
+
+        private void RestartTimer()
+        {
+            var interval = GetTimerIntervalMs();
+
+            _timer.Change(interval, interval);
+        }
+
+        private void StopTimer()
+        {
+            _timer.Change(Timeout.Infinite, Timeout.Infinite);
+        }
+
+        #region Commanding
+
+        public Task<bool> VolumeDown(bool mute = false)
+        {
+            var sendVolume = (Volume - 5) > 0 ? Volume - 5 : 0;
+            if (mute && _muteVol == 0)
+            {
+                sendVolume = 0;
+                _muteVol = Volume;
+            }
+            return SetVolume(sendVolume);
+        }
+
+        public Task<bool> VolumeUp(bool unmute = false)
+        {
+            var sendVolume = (Volume + 5) < 100 ? Volume + 5 : 100;
+            if (unmute && _muteVol > 0)
+                sendVolume = _muteVol;
+            _muteVol = 0;
+            return SetVolume(sendVolume);
+        }
+
+        public Task ToggleMute()
+        {
+            if (_muteVol == 0)
+            {
+                _muteVol = Volume;
+                return SetVolume(0);
+            }
+
+            var tmp = _muteVol;
+            _muteVol = 0;
+            return SetVolume(tmp);
+        }
+
+        public async Task<bool> SetVolume(int value)
+        {
+            var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
+            if (command == null)
+                return true;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
+                .ConfigureAwait(false);
+            Volume = value;
+            return true;
+        }
+
+        public async Task<TimeSpan> Seek(TimeSpan value)
+        {
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
+            if (command == null)
+                return value;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
+                .ConfigureAwait(false);
+
+            return value;
+        }
+
+        public async Task<bool> SetAvTransport(string url, string header, string metaData)
+        {
+            StopTimer();
+
+            TransportState = "STOPPED";
+            CurrentId = "0";
+
+            await Task.Delay(50).ConfigureAwait(false);
+
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
+            if (command == null)
+                return false;
+
+            var dictionary = new Dictionary<string, string>
+            {
+                {"CurrentURI", url},
+                {"CurrentURIMetaData", CreateDidlMeta(metaData)}
+            };
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, url, dictionary), header)
+                .ConfigureAwait(false);
+
+            if (!IsPlaying)
+            {
+                await Task.Delay(50).ConfigureAwait(false);
+                await SetPlay().ConfigureAwait(false);
+            }
+
+            _count = 5;
+            RestartTimer();
+
+            return true;
+        }
+
+        private string CreateDidlMeta(string value)
+        {
+            if (value == null)
+                return String.Empty;
+
+            var escapedData = value.Replace("<", "&lt;").Replace(">", "&gt;");
+
+            return String.Format(BaseDidl, escapedData.Replace("\r\n", ""));
+        }
+
+        private const string BaseDidl = "&lt;DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"&gt;{0}&lt;/DIDL-Lite&gt;";
+
+        public async Task<bool> SetNextAvTransport(string value, string header, string metaData)
+        {
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI");
+            if (command == null)
+                return false;
+
+            var dictionary = new Dictionary<string, string>
+            {
+                {"NextURI", value},
+                {"NextURIMetaData", CreateDidlMeta(metaData)}
+            };
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, value, dictionary), header)
+                .ConfigureAwait(false);
+
+            await Task.Delay(100).ConfigureAwait(false);
+
+            return true;
+        }
+
+        public async Task<bool> SetPlay()
+        {
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
+            if (command == null)
+                return false;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
+                .ConfigureAwait(false);
+
+            _count = 5;
+            return true;
+        }
+
+        public async Task<bool> SetStop()
+        {
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
+            if (command == null)
+                return false;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
+                .ConfigureAwait(false);
+
+            await Task.Delay(50).ConfigureAwait(false);
+            _count = 4;
+            return true;
+        }
+
+        public async Task<bool> SetPause()
+        {
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
+            if (command == null)
+                return false;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0))
+                .ConfigureAwait(false);
+
+            await Task.Delay(50).ConfigureAwait(false);
+            TransportState = "PAUSED_PLAYBACK";
+            return true;
+        }
+
+        #endregion
+
+        #region Get data
+
+        // TODO: What is going on here
+        int _count = 5;
+
+        private async void TimerCallback(object sender)
+        {
+            if (_disposed)
+                return;
+
+            StopTimer();
+
+            try
+            {
+                var hasTrack = await GetPositionInfo().ConfigureAwait(false);
+                
+                // TODO: Why make these requests if hasTrack==false?
+                if (_count > 5)
+                {
+                    await GetTransportInfo().ConfigureAwait(false);
+                    if (!hasTrack)
+                    {
+                        await GetMediaInfo().ConfigureAwait(false);
+                    }
+                    await GetVolume().ConfigureAwait(false);
+                    _count = 0;
+                }
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error updating device info", ex);
+            }
+
+            _count++;
+            if (_disposed)
+                return;
+
+            RestartTimer();
+        }
+
+        private async Task GetVolume()
+        {
+            var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
+            if (command == null)
+                return;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+                .ConfigureAwait(false);
+
+            if (result == null || result.Document == null)
+                return;
+
+            var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
+            var volumeValue = volume == null ? null : volume.Value;
+
+            if (volumeValue == null)
+                return;
+
+            Volume = Int32.Parse(volumeValue);
+
+            //Reset the Mute value if Volume is bigger than zero
+            if (Volume > 0 && _muteVol > 0)
+            {
+                _muteVol = 0;
+            }
+        }
+
+        private async Task GetTransportInfo()
+        {
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
+            if (command == null)
+                return;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+            if (service == null)
+                return;
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+                .ConfigureAwait(false);
+
+            if (result == null || result.Document == null)
+                return;
+
+            var transportState =
+                result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
+
+            var transportStateValue = transportState == null ? null : transportState.Value;
+
+            if (transportStateValue != null)
+                TransportState = transportStateValue;
+
+            UpdateTime = DateTime.UtcNow;
+        }
+
+        private async Task GetMediaInfo()
+        {
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
+            if (command == null)
+                return;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+                .ConfigureAwait(false);
+
+            if (result == null || result.Document == null)
+                return;
+
+            var track = result.Document.Descendants("CurrentURIMetaData").Select(i => i.Value).FirstOrDefault();
+
+            if (String.IsNullOrEmpty(track))
+            {
+                CurrentId = "0";
+                return;
+            }
+
+            var uPnpResponse = XElement.Parse(track);
+
+            var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
+
+            var uTrack = uParser.CreateObjectFromXML(new uParserObject
+            {
+                Type = e.GetValue(uPnpNamespaces.uClass),
+                Element = e
+            });
+
+            if (uTrack != null)
+                CurrentId = uTrack.Id;
+        }
+
+        private async Task<bool> GetPositionInfo()
+        {
+            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
+            if (command == null)
+                return true;
+
+            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+
+            if (service == null)
+            {
+                throw new InvalidOperationException("Unable to find service");
+            }
+
+            var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
+                .ConfigureAwait(false);
+
+            if (result == null || result.Document == null)
+                return true;
+
+            var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
+            var duration = durationElem == null ? null : durationElem.Value;
+
+            if (duration != null)
+            {
+                Duration = TimeSpan.Parse(duration);
+            }
+
+            var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
+            var position = positionElem == null ? null : positionElem.Value;
+
+            if (position != null)
+            {
+                Position = TimeSpan.Parse(position);
+            }
+
+            var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
+                .FirstOrDefault();
+
+            if (String.IsNullOrEmpty(track))
+            {
+                //If track is null, some vendors do this, use GetMediaInfo instead                    
+                return false;
+            }
+
+            var uPnpResponse = XElement.Parse(track);
+
+            var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
+
+            var uTrack = uBaseObject.Create(e);
+
+            if (uTrack == null)
+                return true;
+
+            CurrentId = uTrack.Id;
+
+            return true;
+        }
+
+        #endregion
+
+        #region From XML
+
+        private async Task GetAVProtocolAsync()
+        {
+            var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
+            if (avService == null)
+                return;
+
+            var url = avService.SCPDURL;
+            if (!url.Contains("/"))
+                url = "/dmr/" + url;
+            if (!url.StartsWith("/"))
+                url = "/" + url;
+
+            var httpClient = new SsdpHttpClient(_httpClient);
+            var document = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
+
+            AvCommands = TransportCommands.Create(document);
+        }
+
+        private async Task GetRenderingProtocolAsync()
+        {
+            var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
+
+            if (avService == null)
+                return;
+            string url = avService.SCPDURL;
+            if (!url.Contains("/"))
+                url = "/dmr/" + url;
+            if (!url.StartsWith("/"))
+                url = "/" + url;
+
+            var httpClient = new SsdpHttpClient(_httpClient);
+            var document = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
+
+            RendererCommands = TransportCommands.Create(document);
+        }
+
+        internal TransportCommands AvCommands
+        {
+            get;
+            set;
+        }
+
+        internal TransportCommands RendererCommands
+        {
+            get;
+            set;
+        }
+
+        public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger)
+        {
+            var ssdpHttpClient = new SsdpHttpClient(httpClient);
+
+            var document = await ssdpHttpClient.GetDataAsync(url).ConfigureAwait(false);
+
+            var deviceProperties = new DeviceProperties();
+
+            var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
+            if (name != null)
+                deviceProperties.Name = name.Value;
+
+            var name2 = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
+            if (name2 != null)
+                deviceProperties.Name = name2.Value;
+
+            var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
+            if (model != null)
+                deviceProperties.ModelName = model.Value;
+
+            var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
+            if (modelNumber != null)
+                deviceProperties.ModelNumber = modelNumber.Value;
+
+            var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
+            if (uuid != null)
+                deviceProperties.UUID = uuid.Value;
+
+            var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
+            if (manufacturer != null)
+                deviceProperties.Manufacturer = manufacturer.Value;
+
+            var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
+            if (manufacturerUrl != null)
+                deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
+
+            var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
+            if (presentationUrl != null)
+                deviceProperties.PresentationUrl = presentationUrl.Value;
+
+
+            deviceProperties.BaseUrl = String.Format("http://{0}:{1}", url.Host, url.Port);
+
+            var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
+
+            if (icon != null)
+            {
+                deviceProperties.Icon = uIcon.Create(icon);
+            }
+
+            var isRenderer = false;
+
+            foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
+            {
+                if (services == null)
+                    return null;
+
+                var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
+
+                if (servicesList == null)
+                    return null;
+
+                foreach (var element in servicesList)
+                {
+                    var service = uService.Create(element);
+
+                    if (service != null)
+                    {
+                        deviceProperties.Services.Add(service);
+                        if (service.ServiceId == ServiceAvtransportId)
+                        {
+                            isRenderer = true;
+                        }
+                    }
+                }
+            }
+
+            if (isRenderer)
+            {
+
+                var device = new Device(deviceProperties, httpClient, logger);
+
+                await device.GetRenderingProtocolAsync().ConfigureAwait(false);
+                await device.GetAVProtocolAsync().ConfigureAwait(false);
+
+                return device;
+            }
+
+            return null;
+        }
+
+        #endregion
+
+        #region Events
+
+        public event EventHandler<TransportStateEventArgs> PlaybackChanged;
+        public event EventHandler<CurrentIdEventArgs> CurrentIdChanged;
+
+        private void NotifyPlaybackChanged(bool value)
+        {
+            if (PlaybackChanged != null)
+            {
+                PlaybackChanged.Invoke(this, new TransportStateEventArgs
+                {
+                    Stopped = IsStopped
+                });
+            }
+        }
+
+        private void NotifyCurrentIdChanged(string value)
+        {
+            if (CurrentIdChanged != null)
+                CurrentIdChanged.Invoke(this, new CurrentIdEventArgs(value));
+        }
+
+        #endregion
+
+        #region IDisposable
+
+        bool _disposed;
+        public void Dispose()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+                _timer.Dispose();
+            }
+        }
+
+        #endregion
+
+        public override string ToString()
+        {
+            return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
+        }
+    }
+}

+ 481 - 489
MediaBrowser.Dlna/PlayTo/DlnaController.cs

@@ -1,489 +1,481 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Dlna.PlayTo.Configuration;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Session;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Timers;
-using Timer = System.Timers.Timer;
-
-namespace MediaBrowser.Dlna.PlayTo
-{
-    public class PlayToController : ISessionController, IDisposable
-    {
-        private Device _device;
-        private BaseItem _currentItem = null;
-        private TranscodeSettings[] _transcodeSettings;
-        private readonly SessionInfo _session;
-        private readonly ISessionManager _sessionManager;
-        private readonly IItemRepository _itemRepository;
-        private readonly ILibraryManager _libraryManager;
-        private readonly INetworkManager _networkManager;
-        private readonly ILogger _logger;
-        private bool _playbackStarted = false;
-
-        public bool SupportsMediaRemoteControl
-        {
-            get { return true; }
-        }
-
-        public bool IsSessionActive
-        {
-            get
-            {
-                if (_device == null || _device.UpdateTime == default(DateTime))
-                    return false;
-
-                return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
-            }
-        }
-
-        public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager)
-        {
-            _session = session;
-            _itemRepository = itemRepository;
-            _sessionManager = sessionManager;
-            _libraryManager = libraryManager;
-            _networkManager = networkManager;
-            _logger = logger;
-        }
-
-        public void Init(Device device, TranscodeSettings[] transcodeSettings)
-        {
-            _transcodeSettings = transcodeSettings;
-            _device = device;
-            _device.PlaybackChanged += Device_PlaybackChanged;
-            _device.CurrentIdChanged += Device_CurrentIdChanged;
-            _device.Start();
-
-            _updateTimer = new Timer(1000);
-            _updateTimer.Elapsed += updateTimer_Elapsed;
-            _updateTimer.Start();
-        }
-
-        #region Device EventHandlers & Update Timer
-
-        Timer _updateTimer;
-
-        async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
-        {
-            if (_currentItem == null)
-                return;
-
-            if (e.Stopped == false)
-                await ReportProgress().ConfigureAwait(false);
-
-            else if (e.Stopped && _playbackStarted)
-            {
-                _playbackStarted = false;
-
-                await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
-                {
-                    Item = _currentItem,
-                    SessionId = _session.Id,
-                    PositionTicks = _device.Position.Ticks
-
-                }).ConfigureAwait(false);
-
-                await SetNext().ConfigureAwait(false);
-            }
-        }
-
-        async void Device_CurrentIdChanged(object sender, CurrentIdEventArgs e)
-        {
-            if (e.Id != Guid.Empty)
-            {
-                if (_currentItem != null && _currentItem.Id == e.Id)
-                {
-                    return;
-                }
-
-                var item = _libraryManager.GetItemById(e.Id);
-
-                if (item != null)
-                {
-                    _logger.Debug("{0} - CurrentId {1}", _session.DeviceName, item.Id);
-                    _currentItem = item;
-                    _playbackStarted = false;
-
-                    await ReportProgress().ConfigureAwait(false);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Handles the Elapsed event of the updateTimer control.
-        /// </summary>
-        /// <param name="sender">The source of the event.</param>
-        /// <param name="e">The <see cref="ElapsedEventArgs"/> instance containing the event data.</param>
-        async void updateTimer_Elapsed(object sender, ElapsedEventArgs e)
-        {
-            if (_disposed)
-                return;
-
-            ((Timer)sender).Stop();
-
-
-            if(!IsSessionActive)
-            {
-                //Session is inactive, mark it for Disposal and don't start the elapsed timer.
-                await _sessionManager.ReportSessionEnded(this._session.Id);
-                return;
-            }
-
-            await ReportProgress().ConfigureAwait(false);
-
-            if (!_disposed && IsSessionActive)
-                ((Timer)sender).Start();
-        }
-
-        /// <summary>
-        /// Reports the playback progress.
-        /// </summary>
-        /// <returns></returns>
-        private async Task ReportProgress()
-        {
-            if (_currentItem == null || _device.IsStopped)
-                return;
-
-            if (!_playbackStarted)
-            {
-                await _sessionManager.OnPlaybackStart(new PlaybackInfo { Item = _currentItem, SessionId = _session.Id, CanSeek = true, QueueableMediaTypes = new List<string> { "Audio", "Video" } }).ConfigureAwait(false);
-                _playbackStarted = true;
-            }
-
-            if ((_device.IsPlaying || _device.IsPaused))
-            {
-                var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
-                if (playlistItem != null && playlistItem.Transcode)
-                {
-                    await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
-                    {
-                        Item = _currentItem,
-                        SessionId = _session.Id,
-                        PositionTicks = _device.Position.Ticks + playlistItem.StartPositionTicks,
-                        IsMuted = _device.IsMuted,
-                        IsPaused = _device.IsPaused
-
-                    }).ConfigureAwait(false);
-                }
-                else if (_currentItem != null)
-                {
-                    await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
-                    {
-                        Item = _currentItem,
-                        SessionId = _session.Id,
-                        PositionTicks = _device.Position.Ticks,
-                        IsMuted = _device.IsMuted,
-                        IsPaused = _device.IsPaused
-
-                    }).ConfigureAwait(false);
-                }
-            }
-        }
-
-        #endregion
-
-        #region SendCommands
-
-        public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
-        {
-            _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
-
-            var items = new List<BaseItem>();
-            foreach (string id in command.ItemIds)
-            {
-                AddItemFromId(Guid.Parse(id), items);
-            }
-
-            var playlist = new List<PlaylistItem>();
-            var isFirst = true;
-
-            var serverAddress = GetServerAddress();
-
-            foreach (var item in items)
-            {
-                if (isFirst && command.StartPositionTicks.HasValue)
-                {
-                    playlist.Add(CreatePlaylistItem(item, command.StartPositionTicks.Value, serverAddress));
-                    isFirst = false;
-                }
-                else
-                {
-                    playlist.Add(CreatePlaylistItem(item, 0, serverAddress));
-                }
-            }
-
-            _logger.Debug("{0} - Playlist created", _session.DeviceName);
-
-            if (command.PlayCommand == PlayCommand.PlayLast)
-            {
-                AddItemsToPlaylist(playlist);
-                return Task.FromResult(true);
-            }
-            if (command.PlayCommand == PlayCommand.PlayNext)
-            {
-                AddItemsToPlaylist(playlist);
-                return Task.FromResult(true);
-            }
-
-            _logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
-            return PlayItems(playlist);
-        }
-
-        public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
-        {
-            switch (command.Command)
-            {
-                case PlaystateCommand.Stop:
-                    Playlist.Clear();
-                    return _device.SetStop();
-
-                case PlaystateCommand.Pause:
-                    return _device.SetPause();
-
-                case PlaystateCommand.Unpause:
-                    return _device.SetPlay();
-
-                case PlaystateCommand.Seek:
-                    var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
-                    if (playlistItem != null && playlistItem.Transcode && playlistItem.IsVideo && _currentItem != null)
-                    {
-                        var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
-                        playlistItem.StartPositionTicks = newItem.StartPositionTicks;
-                        playlistItem.StreamUrl = newItem.StreamUrl;
-                        playlistItem.Didl = newItem.Didl;
-                        return _device.SetAvTransport(playlistItem.StreamUrl, playlistItem.DlnaHeaders, playlistItem.Didl);
-
-                    }
-                    return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
-
-
-                case PlaystateCommand.NextTrack:
-                    _currentItem = null;
-                    return SetNext();
-
-                case PlaystateCommand.PreviousTrack:
-                    _currentItem = null;
-                    return SetPrevious();
-            }
-
-            return Task.FromResult(true);
-        }
-
-        public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken)
-        {
-            switch (command)
-            {
-                case SystemCommand.VolumeDown:
-                    return _device.VolumeDown();
-                case SystemCommand.VolumeUp:
-                    return _device.VolumeUp();
-                case SystemCommand.Mute:
-                    return _device.VolumeDown(true);
-                case SystemCommand.Unmute:
-                    return _device.VolumeUp(true);
-                case SystemCommand.ToggleMute:
-                    return _device.ToggleMute();
-                default:
-                    return Task.FromResult(true);
-            }
-        }
-
-        public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
-        {
-            return Task.FromResult(true);
-        }
-
-        public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
-        {
-            return Task.FromResult(true);
-        }
-
-        public Task SendServerRestartNotification(CancellationToken cancellationToken)
-        {
-            return Task.FromResult(true);
-        }
-
-        public Task SendServerShutdownNotification(CancellationToken cancellationToken)
-        {
-            return Task.FromResult(true);
-        }
-
-        public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
-        {
-            return Task.FromResult(true);
-        }
-
-        public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
-        {
-            return Task.FromResult(true);
-        }
-
-        public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
-        {
-            return Task.FromResult(true);
-        }
-
-        #endregion
-
-        #region Playlist
-
-        private List<PlaylistItem> _playlist = new List<PlaylistItem>();
-
-        private List<PlaylistItem> Playlist
-        {
-            get
-            {
-                return _playlist;
-            }
-            set
-            {
-                _playlist = value;
-            }
-        }
-
-        private void AddItemFromId(Guid id, List<BaseItem> list)
-        {
-            var item = _libraryManager.GetItemById(id);
-            if (item.IsFolder)
-            {
-                foreach (var childId in _itemRepository.GetChildren(item.Id))
-                {
-                    AddItemFromId(childId, list);
-                }
-            }
-            else
-            {
-                if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
-                {
-                    list.Add(item);
-                }
-            }
-        }
-
-        private string GetServerAddress()
-        {
-            return string.Format("{0}://{1}:{2}/mediabrowser",
-
-                "http",
-                _networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost",
-                "8096"
-                );
-        }
-
-        private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
-        {
-            var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id }).ToList();
-
-            var playlistItem = PlaylistItem.GetBasicConfig(item, _transcodeSettings);
-            playlistItem.StartPositionTicks = startPostionTicks;
-
-            if (playlistItem.IsAudio)
-                playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress);
-            else
-            {
-                playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
-            }
-
-            var didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams);
-            playlistItem.Didl = didl;
-
-            var header = StreamHelper.GetDlnaHeaders(playlistItem);
-            playlistItem.DlnaHeaders = header;
-            return playlistItem;
-        }
-
-        /// <summary>
-        /// Plays the items.
-        /// </summary>
-        /// <param name="items">The items.</param>
-        /// <returns></returns>
-        private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
-        {
-            Playlist.Clear();
-            Playlist.AddRange(items);
-            await SetNext();
-            return true;
-        }
-
-        /// <summary>
-        /// Adds the items to playlist.
-        /// </summary>
-        /// <param name="items">The items.</param>
-        private void AddItemsToPlaylist(IEnumerable<PlaylistItem> items)
-        {
-            Playlist.AddRange(items);
-        }
-
-        private async Task<bool> SetNext()
-        {
-            if (!Playlist.Any() || Playlist.All(i => i.PlayState != 0))
-            {
-                return true;
-            }
-            var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
-
-            if (currentitem != null)
-            {
-                currentitem.PlayState = 2;
-            }
-
-            var nextTrack = Playlist.FirstOrDefault(i => i.PlayState == 0);
-            if (nextTrack == null)
-            {
-                await _device.SetStop();
-                return true;
-            }
-            nextTrack.PlayState = 1;
-            await _device.SetAvTransport(nextTrack.StreamUrl, nextTrack.DlnaHeaders, nextTrack.Didl);
-            if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode)
-                await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks));
-            return true;
-        }
-
-        public Task<bool> SetPrevious()
-        {
-            if (!Playlist.Any() || Playlist.All(i => i.PlayState != 2))
-                return Task.FromResult(false);
-
-            var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
-
-            var prevTrack = Playlist.LastOrDefault(i => i.PlayState == 2);
-
-            if (currentitem != null)
-            {
-                currentitem.PlayState = 0;
-            }
-
-            if (prevTrack == null)
-                return Task.FromResult(false);
-
-            prevTrack.PlayState = 1;
-            return _device.SetAvTransport(prevTrack.StreamUrl, prevTrack.DlnaHeaders, prevTrack.Didl);
-        }
-
-        #endregion
-
-        private bool _disposed;
-
-        public void Dispose()
-        {
-            if (!_disposed)
-            {
-                _updateTimer.Stop();
-                _disposed = true;
-                _device.Dispose();
-                _logger.Log(LogSeverity.Debug, "PlayTo - Controller disposed");
-            }
-        }
-    }
-}
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Dlna.PlayTo.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Session;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Timers;
+using Timer = System.Timers.Timer;
+
+namespace MediaBrowser.Dlna.PlayTo
+{
+    public class PlayToController : ISessionController, IDisposable
+    {
+        private Device _device;
+        private BaseItem _currentItem = null;
+        private TranscodeSettings[] _transcodeSettings;
+        private readonly SessionInfo _session;
+        private readonly ISessionManager _sessionManager;
+        private readonly IItemRepository _itemRepository;
+        private readonly ILibraryManager _libraryManager;
+        private readonly INetworkManager _networkManager;
+        private readonly ILogger _logger;
+        private bool _playbackStarted = false;
+
+        public bool SupportsMediaRemoteControl
+        {
+            get { return true; }
+        }
+
+        public bool IsSessionActive
+        {
+            get
+            {
+                if (_device == null || _device.UpdateTime == default(DateTime))
+                    return false;
+
+                return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
+            }
+        }
+
+        public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager)
+        {
+            _session = session;
+            _itemRepository = itemRepository;
+            _sessionManager = sessionManager;
+            _libraryManager = libraryManager;
+            _networkManager = networkManager;
+            _logger = logger;
+        }
+
+        public void Init(Device device, TranscodeSettings[] transcodeSettings)
+        {
+            _transcodeSettings = transcodeSettings;
+            _device = device;
+            _device.PlaybackChanged += Device_PlaybackChanged;
+            _device.CurrentIdChanged += Device_CurrentIdChanged;
+            _device.Start();
+
+            _updateTimer = new Timer(1000);
+            _updateTimer.Elapsed += updateTimer_Elapsed;
+            _updateTimer.Start();
+        }
+
+        #region Device EventHandlers & Update Timer
+
+        Timer _updateTimer;
+
+        async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
+        {
+            if (_currentItem == null)
+                return;
+
+            if (e.Stopped == false)
+                await ReportProgress().ConfigureAwait(false);
+
+            else if (e.Stopped && _playbackStarted)
+            {
+                _playbackStarted = false;
+
+                await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
+                {
+                    Item = _currentItem,
+                    SessionId = _session.Id,
+                    PositionTicks = _device.Position.Ticks
+
+                }).ConfigureAwait(false);
+
+                await SetNext().ConfigureAwait(false);
+            }
+        }
+
+        async void Device_CurrentIdChanged(object sender, CurrentIdEventArgs e)
+        {
+            if (e.Id != Guid.Empty)
+            {
+                if (_currentItem != null && _currentItem.Id == e.Id)
+                {
+                    return;
+                }
+
+                var item = _libraryManager.GetItemById(e.Id);
+
+                if (item != null)
+                {
+                    _logger.Debug("{0} - CurrentId {1}", _session.DeviceName, item.Id);
+                    _currentItem = item;
+                    _playbackStarted = false;
+
+                    await ReportProgress().ConfigureAwait(false);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handles the Elapsed event of the updateTimer control.
+        /// </summary>
+        /// <param name="sender">The source of the event.</param>
+        /// <param name="e">The <see cref="ElapsedEventArgs"/> instance containing the event data.</param>
+        async void updateTimer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            if (_disposed)
+                return;
+
+            ((Timer)sender).Stop();
+
+            await ReportProgress().ConfigureAwait(false);
+
+            if (!_disposed && IsSessionActive)
+                ((Timer)sender).Start();
+        }
+
+        /// <summary>
+        /// Reports the playback progress.
+        /// </summary>
+        /// <returns></returns>
+        private async Task ReportProgress()
+        {
+            if (_currentItem == null || _device.IsStopped)
+                return;
+
+            if (!_playbackStarted)
+            {
+                await _sessionManager.OnPlaybackStart(new PlaybackInfo { Item = _currentItem, SessionId = _session.Id, CanSeek = true, QueueableMediaTypes = new List<string> { "Audio", "Video" } }).ConfigureAwait(false);
+                _playbackStarted = true;
+            }
+
+            if ((_device.IsPlaying || _device.IsPaused))
+            {
+                var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
+                if (playlistItem != null && playlistItem.Transcode)
+                {
+                    await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
+                    {
+                        Item = _currentItem,
+                        SessionId = _session.Id,
+                        PositionTicks = _device.Position.Ticks + playlistItem.StartPositionTicks,
+                        IsMuted = _device.IsMuted,
+                        IsPaused = _device.IsPaused
+
+                    }).ConfigureAwait(false);
+                }
+                else if (_currentItem != null)
+                {
+                    await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
+                    {
+                        Item = _currentItem,
+                        SessionId = _session.Id,
+                        PositionTicks = _device.Position.Ticks,
+                        IsMuted = _device.IsMuted,
+                        IsPaused = _device.IsPaused
+
+                    }).ConfigureAwait(false);
+                }
+            }
+        }
+
+        #endregion
+
+        #region SendCommands
+
+        public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
+        {
+            _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
+
+            var items = new List<BaseItem>();
+            foreach (string id in command.ItemIds)
+            {
+                AddItemFromId(Guid.Parse(id), items);
+            }
+
+            var playlist = new List<PlaylistItem>();
+            var isFirst = true;
+
+            var serverAddress = GetServerAddress();
+
+            foreach (var item in items)
+            {
+                if (isFirst && command.StartPositionTicks.HasValue)
+                {
+                    playlist.Add(CreatePlaylistItem(item, command.StartPositionTicks.Value, serverAddress));
+                    isFirst = false;
+                }
+                else
+                {
+                    playlist.Add(CreatePlaylistItem(item, 0, serverAddress));
+                }
+            }
+
+            _logger.Debug("{0} - Playlist created", _session.DeviceName);
+
+            if (command.PlayCommand == PlayCommand.PlayLast)
+            {
+                AddItemsToPlaylist(playlist);
+                return Task.FromResult(true);
+            }
+            if (command.PlayCommand == PlayCommand.PlayNext)
+            {
+                AddItemsToPlaylist(playlist);
+                return Task.FromResult(true);
+            }
+
+            _logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
+            return PlayItems(playlist);
+        }
+
+        public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
+        {
+            switch (command.Command)
+            {
+                case PlaystateCommand.Stop:
+                    Playlist.Clear();
+                    return _device.SetStop();
+
+                case PlaystateCommand.Pause:
+                    return _device.SetPause();
+
+                case PlaystateCommand.Unpause:
+                    return _device.SetPlay();
+
+                case PlaystateCommand.Seek:
+                    var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
+                    if (playlistItem != null && playlistItem.Transcode && playlistItem.IsVideo && _currentItem != null)
+                    {
+                        var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
+                        playlistItem.StartPositionTicks = newItem.StartPositionTicks;
+                        playlistItem.StreamUrl = newItem.StreamUrl;
+                        playlistItem.Didl = newItem.Didl;
+                        return _device.SetAvTransport(playlistItem.StreamUrl, playlistItem.DlnaHeaders, playlistItem.Didl);
+
+                    }
+                    return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
+
+
+                case PlaystateCommand.NextTrack:
+                    _currentItem = null;
+                    return SetNext();
+
+                case PlaystateCommand.PreviousTrack:
+                    _currentItem = null;
+                    return SetPrevious();
+            }
+
+            return Task.FromResult(true);
+        }
+
+        public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken)
+        {
+            switch (command)
+            {
+                case SystemCommand.VolumeDown:
+                    return _device.VolumeDown();
+                case SystemCommand.VolumeUp:
+                    return _device.VolumeUp();
+                case SystemCommand.Mute:
+                    return _device.VolumeDown(true);
+                case SystemCommand.Unmute:
+                    return _device.VolumeUp(true);
+                case SystemCommand.ToggleMute:
+                    return _device.ToggleMute();
+                default:
+                    return Task.FromResult(true);
+            }
+        }
+
+        public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendServerRestartNotification(CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendServerShutdownNotification(CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
+        {
+            return Task.FromResult(true);
+        }
+
+        #endregion
+
+        #region Playlist
+
+        private List<PlaylistItem> _playlist = new List<PlaylistItem>();
+
+        private List<PlaylistItem> Playlist
+        {
+            get
+            {
+                return _playlist;
+            }
+            set
+            {
+                _playlist = value;
+            }
+        }
+
+        private void AddItemFromId(Guid id, List<BaseItem> list)
+        {
+            var item = _libraryManager.GetItemById(id);
+            if (item.IsFolder)
+            {
+                foreach (var childId in _itemRepository.GetChildren(item.Id))
+                {
+                    AddItemFromId(childId, list);
+                }
+            }
+            else
+            {
+                if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
+                {
+                    list.Add(item);
+                }
+            }
+        }
+
+        private string GetServerAddress()
+        {
+            return string.Format("{0}://{1}:{2}/mediabrowser",
+
+                "http",
+                _networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost",
+                "8096"
+                );
+        }
+
+        private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
+        {
+            var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id }).ToList();
+
+            var playlistItem = PlaylistItem.GetBasicConfig(item, _transcodeSettings);
+            playlistItem.StartPositionTicks = startPostionTicks;
+
+            if (playlistItem.IsAudio)
+                playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress);
+            else
+            {
+                playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
+            }
+
+            var didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams);
+            playlistItem.Didl = didl;
+
+            var header = StreamHelper.GetDlnaHeaders(playlistItem);
+            playlistItem.DlnaHeaders = header;
+            return playlistItem;
+        }
+
+        /// <summary>
+        /// Plays the items.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <returns></returns>
+        private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
+        {
+            Playlist.Clear();
+            Playlist.AddRange(items);
+            await SetNext();
+            return true;
+        }
+
+        /// <summary>
+        /// Adds the items to playlist.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        private void AddItemsToPlaylist(IEnumerable<PlaylistItem> items)
+        {
+            Playlist.AddRange(items);
+        }
+
+        private async Task<bool> SetNext()
+        {
+            if (!Playlist.Any() || Playlist.All(i => i.PlayState != 0))
+            {
+                return true;
+            }
+            var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
+
+            if (currentitem != null)
+            {
+                currentitem.PlayState = 2;
+            }
+
+            var nextTrack = Playlist.FirstOrDefault(i => i.PlayState == 0);
+            if (nextTrack == null)
+            {
+                await _device.SetStop();
+                return true;
+            }
+            nextTrack.PlayState = 1;
+            await _device.SetAvTransport(nextTrack.StreamUrl, nextTrack.DlnaHeaders, nextTrack.Didl);
+            if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode)
+                await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks));
+            return true;
+        }
+
+        public Task<bool> SetPrevious()
+        {
+            if (!Playlist.Any() || Playlist.All(i => i.PlayState != 2))
+                return Task.FromResult(false);
+
+            var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
+
+            var prevTrack = Playlist.LastOrDefault(i => i.PlayState == 2);
+
+            if (currentitem != null)
+            {
+                currentitem.PlayState = 0;
+            }
+
+            if (prevTrack == null)
+                return Task.FromResult(false);
+
+            prevTrack.PlayState = 1;
+            return _device.SetAvTransport(prevTrack.StreamUrl, prevTrack.DlnaHeaders, prevTrack.Didl);
+        }
+
+        #endregion
+
+        private bool _disposed;
+
+        public void Dispose()
+        {
+            if (!_disposed)
+            {
+                _updateTimer.Stop();
+                _disposed = true;
+                _device.Dispose();
+                _logger.Log(LogSeverity.Debug, "PlayTo - Controller disposed");
+            }
+        }
+    }
+}

+ 257 - 256
MediaBrowser.sln

@@ -1,256 +1,257 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.21005.1
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F0E0E64C-2A6F-4E35-9533-D53AC07C2CD1}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C5D6ABC-D277-407B-8061-3AA04251D539}"
-	ProjectSection(SolutionItems) = preProject
-		Performance19.psess = Performance19.psess
-	EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget (2)", ".nuget (2)", "{E60FB157-87E2-4A41-8B04-27EA49B63B4D}"
-	ProjectSection(SolutionItems) = preProject
-		.nuget\NuGet.Config = .nuget\NuGet.Config
-		.nuget\NuGet.exe = .nuget\NuGet.exe
-		.nuget\NuGet.targets = .nuget\NuGet.targets
-	EndProjectSection
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common.Implementations", "MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj", "{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Implementations", "MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj", "{2E781478-814D-4A48-9D80-BFF206441A65}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model.net35", "MediaBrowser.Model.net35\MediaBrowser.Model.net35.csproj", "{657B5410-7C3B-4806-9753-D254102CE537}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Tests", "MediaBrowser.Tests\MediaBrowser.Tests.csproj", "{E22BFD35-0FCD-4A85-978A-C22DCD73A081}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model.Portable", "MediaBrowser.Model.Portable\MediaBrowser.Model.Portable.csproj", "{D729ADB1-1C01-428D-B680-8EFACD687B2A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ServerApplication", "MediaBrowser.ServerApplication\MediaBrowser.ServerApplication.csproj", "{94ADE4D3-B7EC-45CD-A200-CC469433072B}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Dlna", "MediaBrowser.Dlna\MediaBrowser.Dlna.csproj", "{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Debug|Mixed Platforms = Debug|Mixed Platforms
-		Debug|Win32 = Debug|Win32
-		Debug|x64 = Debug|x64
-		Debug|x86 = Debug|x86
-		Release|Any CPU = Release|Any CPU
-		Release|Mixed Platforms = Release|Mixed Platforms
-		Release|Win32 = Release|Win32
-		Release|x64 = Release|x64
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.Build.0 = Debug|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Win32.ActiveCfg = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x64.ActiveCfg = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|Any CPU
-		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.Build.0 = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.Build.0 = Debug|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Win32.ActiveCfg = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x64.ActiveCfg = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.ActiveCfg = Release|Any CPU
-		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.Build.0 = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.Build.0 = Debug|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Win32.ActiveCfg = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x64.ActiveCfg = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU
-		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.Build.0 = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.Build.0 = Debug|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Win32.ActiveCfg = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x64.ActiveCfg = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU
-		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.Build.0 = Release|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.Build.0 = Debug|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Win32.ActiveCfg = Release|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x64.ActiveCfg = Release|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.ActiveCfg = Release|Any CPU
-		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.Build.0 = Release|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Any CPU.Build.0 = Release|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Win32.ActiveCfg = Release|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x64.ActiveCfg = Release|Any CPU
-		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.ActiveCfg = Release|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Any CPU.Build.0 = Release|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Win32.ActiveCfg = Release|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x64.ActiveCfg = Release|Any CPU
-		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.ActiveCfg = Release|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Any CPU.Build.0 = Release|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Win32.ActiveCfg = Release|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Release|x64.ActiveCfg = Release|Any CPU
-		{657B5410-7C3B-4806-9753-D254102CE537}.Release|x86.ActiveCfg = Release|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Any CPU.Build.0 = Release|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Win32.ActiveCfg = Release|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x64.ActiveCfg = Release|Any CPU
-		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x86.ActiveCfg = Release|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Win32.ActiveCfg = Release|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x64.ActiveCfg = Release|Any CPU
-		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.ActiveCfg = Release|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Any CPU.Build.0 = Release|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Win32.ActiveCfg = Release|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|x64.ActiveCfg = Release|Any CPU
-		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|x86.ActiveCfg = Release|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Any CPU.ActiveCfg = Debug|x86
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Any CPU.Build.0 = Debug|x86
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Any CPU.ActiveCfg = Release|x86
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Any CPU.Build.0 = Release|x86
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Win32.ActiveCfg = Release|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|x64.ActiveCfg = Release|Any CPU
-		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|x86.ActiveCfg = Release|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Win32.ActiveCfg = Debug|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|x64.ActiveCfg = Debug|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Any CPU.Build.0 = Release|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Win32.ActiveCfg = Release|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x64.ActiveCfg = Release|Any CPU
-		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x86.ActiveCfg = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F0E0E64C-2A6F-4E35-9533-D53AC07C2CD1}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C5D6ABC-D277-407B-8061-3AA04251D539}"
+	ProjectSection(SolutionItems) = preProject
+		Performance19.psess = Performance19.psess
+	EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget (2)", ".nuget (2)", "{E60FB157-87E2-4A41-8B04-27EA49B63B4D}"
+	ProjectSection(SolutionItems) = preProject
+		.nuget\NuGet.Config = .nuget\NuGet.Config
+		.nuget\NuGet.exe = .nuget\NuGet.exe
+		.nuget\NuGet.targets = .nuget\NuGet.targets
+	EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common.Implementations", "MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj", "{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Implementations", "MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj", "{2E781478-814D-4A48-9D80-BFF206441A65}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model.net35", "MediaBrowser.Model.net35\MediaBrowser.Model.net35.csproj", "{657B5410-7C3B-4806-9753-D254102CE537}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Tests", "MediaBrowser.Tests\MediaBrowser.Tests.csproj", "{E22BFD35-0FCD-4A85-978A-C22DCD73A081}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model.Portable", "MediaBrowser.Model.Portable\MediaBrowser.Model.Portable.csproj", "{D729ADB1-1C01-428D-B680-8EFACD687B2A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ServerApplication", "MediaBrowser.ServerApplication\MediaBrowser.ServerApplication.csproj", "{94ADE4D3-B7EC-45CD-A200-CC469433072B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Dlna", "MediaBrowser.Dlna\MediaBrowser.Dlna.csproj", "{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|Mixed Platforms = Debug|Mixed Platforms
+		Debug|Win32 = Debug|Win32
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|Mixed Platforms = Release|Mixed Platforms
+		Release|Win32 = Release|Win32
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.Build.0 = Debug|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Win32.ActiveCfg = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x64.ActiveCfg = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|Any CPU
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.Build.0 = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.Build.0 = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Win32.ActiveCfg = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x64.ActiveCfg = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.ActiveCfg = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.Build.0 = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.Build.0 = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Win32.ActiveCfg = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x64.ActiveCfg = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.Build.0 = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.Build.0 = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Win32.ActiveCfg = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x64.ActiveCfg = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.Build.0 = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.Build.0 = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Win32.ActiveCfg = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x64.ActiveCfg = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.ActiveCfg = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.Build.0 = Release|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Win32.ActiveCfg = Release|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x64.ActiveCfg = Release|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.ActiveCfg = Release|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Win32.ActiveCfg = Release|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x64.ActiveCfg = Release|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.ActiveCfg = Release|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Any CPU.Build.0 = Release|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Release|Win32.ActiveCfg = Release|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Release|x64.ActiveCfg = Release|Any CPU
+		{657B5410-7C3B-4806-9753-D254102CE537}.Release|x86.ActiveCfg = Release|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|Win32.ActiveCfg = Release|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x64.ActiveCfg = Release|Any CPU
+		{E22BFD35-0FCD-4A85-978A-C22DCD73A081}.Release|x86.ActiveCfg = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Win32.ActiveCfg = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x64.ActiveCfg = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.ActiveCfg = Release|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|Win32.ActiveCfg = Release|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|x64.ActiveCfg = Release|Any CPU
+		{D729ADB1-1C01-428D-B680-8EFACD687B2A}.Release|x86.ActiveCfg = Release|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Any CPU.Build.0 = Debug|x86
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Any CPU.ActiveCfg = Release|x86
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Any CPU.Build.0 = Release|x86
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|Win32.ActiveCfg = Release|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|x64.ActiveCfg = Release|Any CPU
+		{94ADE4D3-B7EC-45CD-A200-CC469433072B}.Release|x86.ActiveCfg = Release|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Win32.ActiveCfg = Release|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x64.ActiveCfg = Release|Any CPU
+		{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x86.ActiveCfg = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(Performance) = preSolution
+		HasPerformanceSessions = true
+	EndGlobalSection
+EndGlobal