Device.cs 26 KB

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