|  | @@ -0,0 +1,682 @@
 | 
	
		
			
				|  |  | +using MediaBrowser.Common.Net;
 | 
	
		
			
				|  |  | +using System;
 | 
	
		
			
				|  |  | +using System.Collections.Generic;
 | 
	
		
			
				|  |  | +using System.IO;
 | 
	
		
			
				|  |  | +using System.Linq;
 | 
	
		
			
				|  |  | +using System.Text;
 | 
	
		
			
				|  |  | +using System.Threading.Tasks;
 | 
	
		
			
				|  |  | +using System.Timers;
 | 
	
		
			
				|  |  | +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 _dt;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public DeviceProperties Properties { get; set; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private int _muteVol;
 | 
	
		
			
				|  |  | +        public bool IsMuted
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return _muteVol > 0;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        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
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if (TransportState == "PAUSED" || TransportState == "PAUSED_PLAYBACK")
 | 
	
		
			
				|  |  | +                    return true;
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public bool IsStopped
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (TransportState == "STOPPED");
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public DateTime UpdateTime
 | 
	
		
			
				|  |  | +        { get; private set; }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #endregion
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private readonly IHttpClient _httpClient;
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        #region Constructor & Initializer
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public Device(DeviceProperties deviceProperties)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            Properties = deviceProperties;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        internal void Start()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            UpdateTime = DateTime.UtcNow;
 | 
	
		
			
				|  |  | +            _dt = new Timer(1000);
 | 
	
		
			
				|  |  | +            _dt.Elapsed += dt_Elapsed;
 | 
	
		
			
				|  |  | +            _dt.Start();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #endregion
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #region Commanding
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public Task<bool> VolumeDown(bool mute = false)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var sendVolume = (Volume - 5) > 0 ? Volume - 5 : 0;
 | 
	
		
			
				|  |  | +            if (mute && _muteVol == 0)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                sendVolume = 0;
 | 
	
		
			
				|  |  | +                _muteVol = Volume;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            return SetVolume(sendVolume);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public Task<bool> VolumeUp(bool unmute = false)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var sendVolume = (Volume + 5) < 100 ? Volume + 5 : 100;
 | 
	
		
			
				|  |  | +            if (unmute && _muteVol > 0)
 | 
	
		
			
				|  |  | +                sendVolume = _muteVol;
 | 
	
		
			
				|  |  | +            _muteVol = 0;
 | 
	
		
			
				|  |  | +            return SetVolume(sendVolume);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public Task ToggleMute()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (_muteVol == 0)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                _muteVol = Volume;
 | 
	
		
			
				|  |  | +                return SetVolume(0);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            int tmp = _muteVol;
 | 
	
		
			
				|  |  | +            _muteVol = 0;
 | 
	
		
			
				|  |  | +            return SetVolume(tmp);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public async Task<bool> SetVolume(int value)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = this.Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceRenderingId);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value));
 | 
	
		
			
				|  |  | +            Volume = value;
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public async Task<TimeSpan> Seek(TimeSpan value)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return value;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"));
 | 
	
		
			
				|  |  | +            return value;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public async Task<bool> SetAvTransport(string url, string header, string metaData)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            _dt.Stop();
 | 
	
		
			
				|  |  | +            TransportState = "STOPPED";
 | 
	
		
			
				|  |  | +            CurrentId = "0";
 | 
	
		
			
				|  |  | +            await Task.Delay(50);
 | 
	
		
			
				|  |  | +            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var dictionary = new Dictionary<string, string>
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                {"CurrentURI", url},
 | 
	
		
			
				|  |  | +                {"CurrentURIMetaData", CreateDidlMeta(metaData)}
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, url, dictionary), header);
 | 
	
		
			
				|  |  | +            if (!IsPlaying)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                await Task.Delay(50);
 | 
	
		
			
				|  |  | +                await SetPlay();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            _count = 5;
 | 
	
		
			
				|  |  | +            _dt.Start();
 | 
	
		
			
				|  |  | +            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<bool> SetNextAvTransport(string value, string header, string metaData)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var dictionary = new Dictionary<string, string>();
 | 
	
		
			
				|  |  | +            dictionary.Add("NextURI", value);
 | 
	
		
			
				|  |  | +            dictionary.Add("NextURIMetaData", CreateDidlMeta(metaData));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, value, dictionary), header);
 | 
	
		
			
				|  |  | +            await Task.Delay(100);
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public async Task<bool> SetPlay()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1));
 | 
	
		
			
				|  |  | +            _count = 5;
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public async Task<bool> SetStop()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1));
 | 
	
		
			
				|  |  | +            await Task.Delay(50);
 | 
	
		
			
				|  |  | +            _count = 4;
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public async Task<bool> SetPause()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 0));
 | 
	
		
			
				|  |  | +            await Task.Delay(50);
 | 
	
		
			
				|  |  | +            TransportState = "PAUSED_PLAYBACK";
 | 
	
		
			
				|  |  | +            return true;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #endregion
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #region Get data
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        int _count = 5;
 | 
	
		
			
				|  |  | +        async void dt_Elapsed(object sender, ElapsedEventArgs e)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (_disposed)
 | 
	
		
			
				|  |  | +                return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            ((Timer)sender).Stop();
 | 
	
		
			
				|  |  | +            var hasTrack = await GetPositionInfo();
 | 
	
		
			
				|  |  | +            if (_count > 4)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                await GetTransportInfo();
 | 
	
		
			
				|  |  | +                if (!hasTrack)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    await GetMediaInfo();
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                await GetVolume();
 | 
	
		
			
				|  |  | +                _count = 0;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            _count++;
 | 
	
		
			
				|  |  | +            if (_disposed)
 | 
	
		
			
				|  |  | +                return;
 | 
	
		
			
				|  |  | +            ((Timer)sender).Start();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        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);
 | 
	
		
			
				|  |  | +            try
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
 | 
	
		
			
				|  |  | +                if (result == null)
 | 
	
		
			
				|  |  | +                    return;
 | 
	
		
			
				|  |  | +                var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").FirstOrDefault().Element("CurrentVolume").Value;
 | 
	
		
			
				|  |  | +                if (volume == null)
 | 
	
		
			
				|  |  | +                    return;
 | 
	
		
			
				|  |  | +                Volume = Int32.Parse(volume);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                //Reset the Mute value if Volume is bigger than zero
 | 
	
		
			
				|  |  | +                if (Volume > 0 && _muteVol > 0)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    _muteVol = 0;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            catch { }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private async Task GetTransportInfo()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = this.Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +            if (service == null)
 | 
	
		
			
				|  |  | +                return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
 | 
	
		
			
				|  |  | +            try
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var transportState = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").FirstOrDefault().Element("CurrentTransportState").Value;
 | 
	
		
			
				|  |  | +                if (transportState != null)
 | 
	
		
			
				|  |  | +                    TransportState = transportState;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            catch { }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (result != null)
 | 
	
		
			
				|  |  | +                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);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
 | 
	
		
			
				|  |  | +            try
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault().Value;
 | 
	
		
			
				|  |  | +                if (String.IsNullOrEmpty(track))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    CurrentId = "0";
 | 
	
		
			
				|  |  | +                    return;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                XElement uPnpResponse = XElement.Parse((String)track);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                var e = uPnpResponse.Element(uPnpNamespaces.items);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if (e == null)
 | 
	
		
			
				|  |  | +                    e = uPnpResponse;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                var uTrack = uParser.CreateObjectFromXML(new uParserObject { Type = e.Element(uPnpNamespaces.uClass).Value, Element = e });
 | 
	
		
			
				|  |  | +                if (uTrack != null)
 | 
	
		
			
				|  |  | +                    CurrentId = uTrack.Id;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            catch { }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private async Task<bool> GetPositionInfo()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
 | 
	
		
			
				|  |  | +            if (command == null)
 | 
	
		
			
				|  |  | +                return true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var service = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var result = await SsdpHttpClient.SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType));
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            try
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var duration = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").FirstOrDefault().Element("TrackDuration").Value;
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +                if (duration != null)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Duration = TimeSpan.Parse(duration);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                var position = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").FirstOrDefault().Element("RelTime").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;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            catch { return false; }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #endregion
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #region From XML
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        internal async Task GetAVProtocolAsync()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var avService = Properties.Services.FirstOrDefault(s => s.ServiceId == ServiceAvtransportId);
 | 
	
		
			
				|  |  | +            if (avService == null)
 | 
	
		
			
				|  |  | +                return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            string url = avService.SCPDURL;
 | 
	
		
			
				|  |  | +            if (!url.Contains("/"))
 | 
	
		
			
				|  |  | +                url = "/dmr/" + url;
 | 
	
		
			
				|  |  | +            if (!url.StartsWith("/"))
 | 
	
		
			
				|  |  | +                url = "/" + url;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var httpClient = new SsdpHttpClient();
 | 
	
		
			
				|  |  | +            var stream = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (stream == null)
 | 
	
		
			
				|  |  | +                return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            XDocument document = httpClient.ParseStream(stream);
 | 
	
		
			
				|  |  | +            stream.Dispose();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            AvCommands = TransportCommands.Create(document);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        internal 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();
 | 
	
		
			
				|  |  | +            var stream = await httpClient.GetDataAsync(new Uri(Properties.BaseUrl + url));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (stream == null)
 | 
	
		
			
				|  |  | +                return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            XDocument document = httpClient.ParseStream(stream);
 | 
	
		
			
				|  |  | +            stream.Dispose();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            RendererCommands = TransportCommands.Create(document);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        internal TransportCommands AvCommands
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get;
 | 
	
		
			
				|  |  | +            set;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        internal TransportCommands RendererCommands
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get;
 | 
	
		
			
				|  |  | +            set;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public static async Task<Device> CreateuPnpDeviceAsync(Uri url)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var httpClient = new SsdpHttpClient();
 | 
	
		
			
				|  |  | +            var stream = await httpClient.GetDataAsync(url);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (stream == null)
 | 
	
		
			
				|  |  | +                return null;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var document = httpClient.ParseStream(stream);
 | 
	
		
			
				|  |  | +            stream.Dispose();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            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);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                await device.GetRenderingProtocolAsync();
 | 
	
		
			
				|  |  | +                await device.GetAVProtocolAsync();
 | 
	
		
			
				|  |  | +                return device;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            return null;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #endregion
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #region Events
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public event EventHandler<TransportStateEventArgs> PlaybackChanged;
 | 
	
		
			
				|  |  | +        public event EventHandler<CurrentIdEventArgs> CurrentIdChanged;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private void NotifyPlaybackChanged(bool value)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (PlaybackChanged != null)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                PlaybackChanged.Invoke(this, new TransportStateEventArgs
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Stopped = IsStopped
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private void NotifyCurrentIdChanged(string value)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (CurrentIdChanged != null)
 | 
	
		
			
				|  |  | +                CurrentIdChanged.Invoke(this, new CurrentIdEventArgs(value));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #endregion
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #region IDisposable
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        bool _disposed;
 | 
	
		
			
				|  |  | +        public void Dispose()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (!_disposed)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                _disposed = true;
 | 
	
		
			
				|  |  | +                _dt.Stop();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        #endregion
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public override string ToString()
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private XDocument ParseStream(Stream stream)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var reader = new StreamReader(stream, Encoding.UTF8);
 | 
	
		
			
				|  |  | +            try
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var doc = XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
 | 
	
		
			
				|  |  | +                stream.Dispose();
 | 
	
		
			
				|  |  | +                return doc;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            catch
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            return null;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 |