Device.cs 30 KB

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