Device.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. using System.Globalization;
  2. using MediaBrowser.Common.Net;
  3. using MediaBrowser.Controller.Configuration;
  4. using MediaBrowser.Model.Logging;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using System.Xml.Linq;
  11. namespace MediaBrowser.Dlna.PlayTo
  12. {
  13. public class Device : IDisposable
  14. {
  15. const string ServiceAvtransportType = "urn:schemas-upnp-org:service:AVTransport:1";
  16. const string ServiceRenderingType = "urn:schemas-upnp-org:service:RenderingControl:1";
  17. #region Fields & Properties
  18. private Timer _timer;
  19. public DeviceInfo Properties { get; set; }
  20. private int _muteVol;
  21. public bool IsMuted
  22. {
  23. get
  24. {
  25. return _muteVol > 0;
  26. }
  27. }
  28. private string _currentId = String.Empty;
  29. public string CurrentId
  30. {
  31. get
  32. {
  33. return _currentId;
  34. }
  35. set
  36. {
  37. if (_currentId == value)
  38. return;
  39. _currentId = value;
  40. NotifyCurrentIdChanged(value);
  41. }
  42. }
  43. public int Volume { get; set; }
  44. public TimeSpan Duration { get; set; }
  45. private TimeSpan _position = TimeSpan.FromSeconds(0);
  46. public TimeSpan Position
  47. {
  48. get
  49. {
  50. return _position;
  51. }
  52. set
  53. {
  54. _position = value;
  55. }
  56. }
  57. private string _transportState = String.Empty;
  58. public string TransportState
  59. {
  60. get
  61. {
  62. return _transportState;
  63. }
  64. set
  65. {
  66. if (_transportState == value)
  67. return;
  68. _transportState = value;
  69. if (value == TRANSPORTSTATE.PLAYING || value == TRANSPORTSTATE.STOPPED)
  70. NotifyPlaybackChanged(value == TRANSPORTSTATE.STOPPED);
  71. }
  72. }
  73. public bool IsPlaying
  74. {
  75. get
  76. {
  77. return TransportState == TRANSPORTSTATE.PLAYING;
  78. }
  79. }
  80. public bool IsTransitioning
  81. {
  82. get
  83. {
  84. return (TransportState == TRANSPORTSTATE.TRANSITIONING);
  85. }
  86. }
  87. public bool IsPaused
  88. {
  89. get
  90. {
  91. return TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK;
  92. }
  93. }
  94. public bool IsStopped
  95. {
  96. get
  97. {
  98. return TransportState == TRANSPORTSTATE.STOPPED;
  99. }
  100. }
  101. public DateTime UpdateTime { get; private set; }
  102. #endregion
  103. private readonly IHttpClient _httpClient;
  104. private readonly ILogger _logger;
  105. private readonly IServerConfigurationManager _config;
  106. public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
  107. {
  108. Properties = deviceProperties;
  109. _httpClient = httpClient;
  110. _logger = logger;
  111. _config = config;
  112. }
  113. private int GetPlaybackTimerIntervalMs()
  114. {
  115. return 2000;
  116. }
  117. private int GetInactiveTimerIntervalMs()
  118. {
  119. return 20000;
  120. }
  121. public void Start()
  122. {
  123. UpdateTime = DateTime.UtcNow;
  124. var interval = GetPlaybackTimerIntervalMs();
  125. _timer = new Timer(TimerCallback, null, interval, interval);
  126. }
  127. private void RestartTimer()
  128. {
  129. var interval = GetPlaybackTimerIntervalMs();
  130. _timer.Change(interval, interval);
  131. }
  132. /// <summary>
  133. /// Restarts the timer in inactive mode.
  134. /// </summary>
  135. private void RestartTimerInactive()
  136. {
  137. var interval = GetInactiveTimerIntervalMs();
  138. _timer.Change(interval, interval);
  139. }
  140. private void StopTimer()
  141. {
  142. _timer.Change(Timeout.Infinite, Timeout.Infinite);
  143. }
  144. #region Commanding
  145. public Task<bool> VolumeDown(bool mute = false)
  146. {
  147. var sendVolume = (Volume - 5) > 0 ? Volume - 5 : 0;
  148. if (mute && _muteVol == 0)
  149. {
  150. sendVolume = 0;
  151. _muteVol = Volume;
  152. }
  153. return SetVolume(sendVolume);
  154. }
  155. public Task<bool> VolumeUp(bool unmute = false)
  156. {
  157. var sendVolume = (Volume + 5) < 100 ? Volume + 5 : 100;
  158. if (unmute && _muteVol > 0)
  159. sendVolume = _muteVol;
  160. _muteVol = 0;
  161. return SetVolume(sendVolume);
  162. }
  163. public Task ToggleMute()
  164. {
  165. if (_muteVol == 0)
  166. {
  167. _muteVol = Volume;
  168. return SetVolume(0);
  169. }
  170. var tmp = _muteVol;
  171. _muteVol = 0;
  172. return SetVolume(tmp);
  173. }
  174. public async Task<bool> SetVolume(int value)
  175. {
  176. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
  177. if (command == null)
  178. return true;
  179. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceRenderingType);
  180. if (service == null)
  181. {
  182. throw new InvalidOperationException("Unable to find service");
  183. }
  184. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
  185. .ConfigureAwait(false);
  186. Volume = value;
  187. return true;
  188. }
  189. public async Task<TimeSpan> Seek(TimeSpan value)
  190. {
  191. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
  192. if (command == null)
  193. return value;
  194. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  195. if (service == null)
  196. {
  197. throw new InvalidOperationException("Unable to find service");
  198. }
  199. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, String.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
  200. .ConfigureAwait(false);
  201. return value;
  202. }
  203. public async Task<bool> SetAvTransport(string url, string header, string metaData)
  204. {
  205. StopTimer();
  206. await SetStop().ConfigureAwait(false);
  207. CurrentId = null;
  208. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
  209. if (command == null)
  210. return false;
  211. var dictionary = new Dictionary<string, string>
  212. {
  213. {"CurrentURI", url},
  214. {"CurrentURIMetaData", CreateDidlMeta(metaData)}
  215. };
  216. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  217. if (service == null)
  218. {
  219. throw new InvalidOperationException("Unable to find service");
  220. }
  221. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, url, dictionary), header)
  222. .ConfigureAwait(false);
  223. await Task.Delay(50).ConfigureAwait(false);
  224. await SetPlay().ConfigureAwait(false);
  225. _lapsCount = GetLapsCount();
  226. RestartTimer();
  227. return true;
  228. }
  229. private string CreateDidlMeta(string value)
  230. {
  231. if (value == null)
  232. return String.Empty;
  233. var escapedData = value.Replace("<", "&lt;").Replace(">", "&gt;");
  234. return String.Format(BaseDidl, escapedData.Replace("\r\n", ""));
  235. }
  236. private const string BaseDidl = "&lt;DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"&gt;{0}&lt;/DIDL-Lite&gt;";
  237. public async Task<bool> SetNextAvTransport(string value, string header, string metaData)
  238. {
  239. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI");
  240. if (command == null)
  241. return false;
  242. var dictionary = new Dictionary<string, string>
  243. {
  244. {"NextURI", value},
  245. {"NextURIMetaData", CreateDidlMeta(metaData)}
  246. };
  247. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  248. if (service == null)
  249. {
  250. throw new InvalidOperationException("Unable to find service");
  251. }
  252. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, value, dictionary), header)
  253. .ConfigureAwait(false);
  254. await Task.Delay(100).ConfigureAwait(false);
  255. return true;
  256. }
  257. public async Task<bool> SetPlay()
  258. {
  259. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
  260. if (command == null)
  261. return false;
  262. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  263. if (service == null)
  264. {
  265. throw new InvalidOperationException("Unable to find service");
  266. }
  267. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
  268. .ConfigureAwait(false);
  269. _lapsCount = GetLapsCount();
  270. return true;
  271. }
  272. public async Task<bool> SetStop()
  273. {
  274. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
  275. if (command == null)
  276. return false;
  277. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  278. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
  279. .ConfigureAwait(false);
  280. await Task.Delay(50).ConfigureAwait(false);
  281. return true;
  282. }
  283. public async Task<bool> SetPause()
  284. {
  285. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
  286. if (command == null)
  287. return false;
  288. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  289. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
  290. .ConfigureAwait(false);
  291. await Task.Delay(50).ConfigureAwait(false);
  292. TransportState = "PAUSED_PLAYBACK";
  293. return true;
  294. }
  295. #endregion
  296. #region Get data
  297. private int GetLapsCount()
  298. {
  299. // No need to get all data every lap, just every X time.
  300. return 10;
  301. }
  302. int _lapsCount = 0;
  303. private async void TimerCallback(object sender)
  304. {
  305. if (_disposed)
  306. return;
  307. StopTimer();
  308. try
  309. {
  310. await GetTransportInfo().ConfigureAwait(false);
  311. //If we're not playing anything no need to get additional data
  312. if (TransportState != TRANSPORTSTATE.STOPPED)
  313. {
  314. var hasTrack = await GetPositionInfo().ConfigureAwait(false);
  315. // TODO: Why make these requests if hasTrack==false?
  316. // TODO ANSWER Some vendors don't include track in GetPositionInfo, use GetMediaInfo instead.
  317. if (_lapsCount > GetLapsCount())
  318. {
  319. if (!hasTrack)
  320. {
  321. await GetMediaInfo().ConfigureAwait(false);
  322. }
  323. await GetVolume().ConfigureAwait(false);
  324. _lapsCount = 0;
  325. }
  326. }
  327. }
  328. catch (Exception ex)
  329. {
  330. _logger.ErrorException("Error updating device info", ex);
  331. }
  332. _lapsCount++;
  333. if (_disposed)
  334. return;
  335. //If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
  336. if (TransportState != TRANSPORTSTATE.STOPPED)
  337. RestartTimer();
  338. else
  339. RestartTimerInactive();
  340. }
  341. private async Task GetVolume()
  342. {
  343. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
  344. if (command == null)
  345. return;
  346. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceRenderingType);
  347. if (service == null)
  348. {
  349. throw new InvalidOperationException("Unable to find service");
  350. }
  351. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
  352. .ConfigureAwait(false);
  353. if (result == null || result.Document == null)
  354. return;
  355. var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
  356. var volumeValue = volume == null ? null : volume.Value;
  357. if (string.IsNullOrWhiteSpace(volumeValue))
  358. return;
  359. Volume = int.Parse(volumeValue, UsCulture);
  360. //Reset the Mute value if Volume is bigger than zero
  361. if (Volume > 0 && _muteVol > 0)
  362. {
  363. _muteVol = 0;
  364. }
  365. }
  366. private async Task GetTransportInfo()
  367. {
  368. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
  369. if (command == null)
  370. return;
  371. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  372. if (service == null)
  373. return;
  374. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
  375. .ConfigureAwait(false);
  376. if (result == null || result.Document == null)
  377. return;
  378. var transportState =
  379. result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
  380. var transportStateValue = transportState == null ? null : transportState.Value;
  381. if (transportStateValue != null)
  382. TransportState = transportStateValue;
  383. UpdateTime = DateTime.UtcNow;
  384. }
  385. private async Task GetMediaInfo()
  386. {
  387. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
  388. if (command == null)
  389. return;
  390. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  391. if (service == null)
  392. {
  393. throw new InvalidOperationException("Unable to find service");
  394. }
  395. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
  396. .ConfigureAwait(false);
  397. if (result == null || result.Document == null)
  398. return;
  399. var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
  400. if (track == null)
  401. {
  402. CurrentId = null;
  403. return;
  404. }
  405. var e = track.Element(uPnpNamespaces.items) ?? track;
  406. var uTrack = uParser.CreateObjectFromXML(new uParserObject
  407. {
  408. Type = e.GetValue(uPnpNamespaces.uClass),
  409. Element = e
  410. });
  411. if (uTrack != null)
  412. CurrentId = uTrack.Id;
  413. }
  414. private async Task<bool> GetPositionInfo()
  415. {
  416. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
  417. if (command == null)
  418. return true;
  419. var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  420. if (service == null)
  421. {
  422. throw new InvalidOperationException("Unable to find service");
  423. }
  424. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType))
  425. .ConfigureAwait(false);
  426. if (result == null || result.Document == null)
  427. return true;
  428. var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
  429. var duration = durationElem == null ? null : durationElem.Value;
  430. if (!string.IsNullOrWhiteSpace(duration) && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
  431. {
  432. Duration = TimeSpan.Parse(duration, UsCulture);
  433. }
  434. var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
  435. var position = positionElem == null ? null : positionElem.Value;
  436. if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
  437. {
  438. Position = TimeSpan.Parse(position, UsCulture);
  439. }
  440. var track = result.Document.Descendants("TrackMetaData").FirstOrDefault();
  441. if (track == null)
  442. {
  443. //If track is null, some vendors do this, use GetMediaInfo instead
  444. return false;
  445. }
  446. var e = track.Element(uPnpNamespaces.items) ?? track;
  447. var uTrack = CreateUBaseObject(e);
  448. if (uTrack == null)
  449. return true;
  450. CurrentId = uTrack.Id;
  451. return true;
  452. }
  453. private static uBaseObject CreateUBaseObject(XElement container)
  454. {
  455. if (container == null)
  456. {
  457. throw new ArgumentNullException("container");
  458. }
  459. return new uBaseObject
  460. {
  461. Id = container.GetAttributeValue(uPnpNamespaces.Id),
  462. ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
  463. Title = container.GetValue(uPnpNamespaces.title),
  464. IconUrl = container.GetValue(uPnpNamespaces.Artwork),
  465. SecondText = "",
  466. Url = container.GetValue(uPnpNamespaces.Res),
  467. ProtocolInfo = GetProtocolInfo(container),
  468. MetaData = container.ToString()
  469. };
  470. }
  471. private static string[] GetProtocolInfo(XElement container)
  472. {
  473. if (container == null)
  474. {
  475. throw new ArgumentNullException("container");
  476. }
  477. var resElement = container.Element(uPnpNamespaces.Res);
  478. if (resElement != null)
  479. {
  480. var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo);
  481. if (info != null && !string.IsNullOrWhiteSpace(info.Value))
  482. {
  483. return info.Value.Split(':');
  484. }
  485. }
  486. return new string[4];
  487. }
  488. #endregion
  489. #region From XML
  490. private async Task GetAVProtocolAsync()
  491. {
  492. var avService = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
  493. if (avService == null)
  494. return;
  495. var url = avService.ScpdUrl;
  496. if (!url.Contains("/"))
  497. url = "/dmr/" + url;
  498. if (!url.StartsWith("/"))
  499. url = "/" + url;
  500. var httpClient = new SsdpHttpClient(_httpClient, _config);
  501. var document = await httpClient.GetDataAsync(Properties.BaseUrl + url);
  502. AvCommands = TransportCommands.Create(document);
  503. }
  504. private async Task GetRenderingProtocolAsync()
  505. {
  506. var avService = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceRenderingType);
  507. if (avService == null)
  508. return;
  509. string url = avService.ScpdUrl;
  510. if (!url.Contains("/"))
  511. url = "/dmr/" + url;
  512. if (!url.StartsWith("/"))
  513. url = "/" + url;
  514. var httpClient = new SsdpHttpClient(_httpClient, _config);
  515. var document = await httpClient.GetDataAsync(Properties.BaseUrl + url);
  516. RendererCommands = TransportCommands.Create(document);
  517. }
  518. private TransportCommands AvCommands
  519. {
  520. get;
  521. set;
  522. }
  523. internal TransportCommands RendererCommands
  524. {
  525. get;
  526. set;
  527. }
  528. public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger)
  529. {
  530. var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
  531. var document = await ssdpHttpClient.GetDataAsync(url.ToString()).ConfigureAwait(false);
  532. var deviceProperties = new DeviceInfo();
  533. var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
  534. if (name != null)
  535. deviceProperties.Name = name.Value;
  536. var name2 = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
  537. if (name2 != null)
  538. deviceProperties.Name = name2.Value;
  539. var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
  540. if (model != null)
  541. deviceProperties.ModelName = model.Value;
  542. var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
  543. if (modelNumber != null)
  544. deviceProperties.ModelNumber = modelNumber.Value;
  545. var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
  546. if (uuid != null)
  547. deviceProperties.UUID = uuid.Value;
  548. var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
  549. if (manufacturer != null)
  550. deviceProperties.Manufacturer = manufacturer.Value;
  551. var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
  552. if (manufacturerUrl != null)
  553. deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
  554. var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
  555. if (presentationUrl != null)
  556. deviceProperties.PresentationUrl = presentationUrl.Value;
  557. var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
  558. if (modelUrl != null)
  559. deviceProperties.ModelUrl = modelUrl.Value;
  560. var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
  561. if (serialNumber != null)
  562. deviceProperties.SerialNumber = serialNumber.Value;
  563. var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
  564. if (modelDescription != null)
  565. deviceProperties.ModelDescription = modelDescription.Value;
  566. deviceProperties.BaseUrl = String.Format("http://{0}:{1}", url.Host, url.Port);
  567. var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
  568. if (icon != null)
  569. {
  570. deviceProperties.Icon = CreateIcon(icon);
  571. }
  572. var isRenderer = false;
  573. foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
  574. {
  575. if (services == null)
  576. return null;
  577. var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
  578. if (servicesList == null)
  579. return null;
  580. foreach (var element in servicesList)
  581. {
  582. var service = Create(element);
  583. if (service != null)
  584. {
  585. deviceProperties.Services.Add(service);
  586. if (service.ServiceType == ServiceAvtransportType)
  587. {
  588. isRenderer = true;
  589. }
  590. }
  591. }
  592. }
  593. if (isRenderer)
  594. {
  595. var device = new Device(deviceProperties, httpClient, logger, config);
  596. await device.GetRenderingProtocolAsync().ConfigureAwait(false);
  597. await device.GetAVProtocolAsync().ConfigureAwait(false);
  598. return device;
  599. }
  600. return null;
  601. }
  602. #endregion
  603. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  604. private static DeviceIcon CreateIcon(XElement element)
  605. {
  606. if (element == null)
  607. {
  608. throw new ArgumentNullException("element");
  609. }
  610. var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype"));
  611. var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width"));
  612. var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height"));
  613. var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth"));
  614. var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url"));
  615. var widthValue = int.Parse(width, NumberStyles.Any, UsCulture);
  616. var heightValue = int.Parse(height, NumberStyles.Any, UsCulture);
  617. return new DeviceIcon
  618. {
  619. Depth = depth,
  620. Height = heightValue,
  621. MimeType = mimeType,
  622. Url = url,
  623. Width = widthValue
  624. };
  625. }
  626. private static DeviceService Create(XElement element)
  627. {
  628. var type = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceType"));
  629. var id = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceId"));
  630. var scpdUrl = element.GetDescendantValue(uPnpNamespaces.ud.GetName("SCPDURL"));
  631. var controlURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("controlURL"));
  632. var eventSubURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("eventSubURL"));
  633. return new DeviceService(type, id, scpdUrl, controlURL, eventSubURL);
  634. }
  635. #region Events
  636. public event EventHandler<TransportStateEventArgs> PlaybackChanged;
  637. public event EventHandler<CurrentIdEventArgs> CurrentIdChanged;
  638. private void NotifyPlaybackChanged(bool value)
  639. {
  640. if (PlaybackChanged != null)
  641. {
  642. PlaybackChanged.Invoke(this, new TransportStateEventArgs
  643. {
  644. Stopped = IsStopped
  645. });
  646. }
  647. }
  648. private void NotifyCurrentIdChanged(string value)
  649. {
  650. if (CurrentIdChanged != null)
  651. CurrentIdChanged.Invoke(this, new CurrentIdEventArgs { Id = value });
  652. }
  653. #endregion
  654. #region IDisposable
  655. bool _disposed;
  656. public void Dispose()
  657. {
  658. if (!_disposed)
  659. {
  660. _disposed = true;
  661. _timer.Dispose();
  662. }
  663. }
  664. #endregion
  665. public override string ToString()
  666. {
  667. return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
  668. }
  669. private class TRANSPORTSTATE
  670. {
  671. public const string STOPPED = "STOPPED";
  672. public const string PLAYING = "PLAYING";
  673. public const string TRANSITIONING = "TRANSITIONING";
  674. public const string PAUSED_PLAYBACK = "PAUSED_PLAYBACK";
  675. public const string PAUSED = "PAUSED";
  676. }
  677. }
  678. }