From 6bbf2d115e3c2a5c570da8b1fe4cc8cc8ef2d681 Mon Sep 17 00:00:00 2001 From: 7illusions Date: Mon, 10 Mar 2014 11:06:51 +0100 Subject: [PATCH] Improved polling and Session handling --- MediaBrowser.Dlna/PlayTo/Device.cs | 1541 ++++++++++---------- MediaBrowser.Dlna/PlayTo/DlnaController.cs | 970 ++++++------ MediaBrowser.sln | 513 ++++--- 3 files changed, 1535 insertions(+), 1489 deletions(-) diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index dd416b5a44..6b5e8de9f8 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -1,751 +1,790 @@ -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 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 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 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 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 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 - { - {"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("<", "<").Replace(">", ">"); - - return String.Format(BaseDidl, escapedData.Replace("\r\n", "")); - } - - private const string BaseDidl = "<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/\">{0}</DIDL-Lite>"; - - public async Task 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 - { - {"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 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 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 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 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 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 PlaybackChanged; - public event EventHandler 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); - } - } -} +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); + } + + + /// + /// Restarts the timer in inactive mode. + /// + private void RestartTimerInactive() + { + var interval = GetInactiveTimerIntervalMs(); + + _timer.Change(interval, interval); + } + + private void StopTimer() + { + _timer.Change(Timeout.Infinite, Timeout.Infinite); + } + + #region Commanding + + public Task 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 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 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 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 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 + { + {"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("<", "<").Replace(">", ">"); + + return String.Format(BaseDidl, escapedData.Replace("\r\n", "")); + } + + private const string BaseDidl = "<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/\">{0}</DIDL-Lite>"; + + public async Task 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 + { + {"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 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 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 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 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 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 PlaybackChanged; + public event EventHandler 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"; + } + + } +} diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 48ef5e5897..8bf9ec9f6a 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -1,481 +1,489 @@ -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); - } - } - } - - /// - /// Handles the Elapsed event of the updateTimer control. - /// - /// The source of the event. - /// The instance containing the event data. - async void updateTimer_Elapsed(object sender, ElapsedEventArgs e) - { - if (_disposed) - return; - - ((Timer)sender).Stop(); - - await ReportProgress().ConfigureAwait(false); - - if (!_disposed && IsSessionActive) - ((Timer)sender).Start(); - } - - /// - /// Reports the playback progress. - /// - /// - 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 { "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(); - foreach (string id in command.ItemIds) - { - AddItemFromId(Guid.Parse(id), items); - } - - var playlist = new List(); - 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 _playlist = new List(); - - private List Playlist - { - get - { - return _playlist; - } - set - { - _playlist = value; - } - } - - private void AddItemFromId(Guid id, List 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; - } - - /// - /// Plays the items. - /// - /// The items. - /// - private async Task PlayItems(IEnumerable items) - { - Playlist.Clear(); - Playlist.AddRange(items); - await SetNext(); - return true; - } - - /// - /// Adds the items to playlist. - /// - /// The items. - private void AddItemsToPlaylist(IEnumerable items) - { - Playlist.AddRange(items); - } - - private async Task 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 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); + } + } + } + + /// + /// Handles the Elapsed event of the updateTimer control. + /// + /// The source of the event. + /// The instance containing the event data. + 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(); + } + + /// + /// Reports the playback progress. + /// + /// + 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 { "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(); + foreach (string id in command.ItemIds) + { + AddItemFromId(Guid.Parse(id), items); + } + + var playlist = new List(); + 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 _playlist = new List(); + + private List Playlist + { + get + { + return _playlist; + } + set + { + _playlist = value; + } + } + + private void AddItemFromId(Guid id, List 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; + } + + /// + /// Plays the items. + /// + /// The items. + /// + private async Task PlayItems(IEnumerable items) + { + Playlist.Clear(); + Playlist.AddRange(items); + await SetNext(); + return true; + } + + /// + /// Adds the items to playlist. + /// + /// The items. + private void AddItemsToPlaylist(IEnumerable items) + { + Playlist.AddRange(items); + } + + private async Task 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 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"); + } + } + } +} diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 7ac1580659..594179fd9e 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,257 +1,256 @@ - -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 + +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