Device.cs 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller.Configuration;
  3. using Emby.Dlna.Common;
  4. using Emby.Dlna.Ssdp;
  5. using MediaBrowser.Model.Logging;
  6. using MediaBrowser.Model.Net;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Globalization;
  10. using System.Linq;
  11. using System.Net;
  12. using System.Security;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using System.Xml.Linq;
  16. using Emby.Dlna.Server;
  17. using MediaBrowser.Model.Threading;
  18. using MediaBrowser.Model.Extensions;
  19. namespace Emby.Dlna.PlayTo
  20. {
  21. public class Device : IDisposable
  22. {
  23. #region Fields & Properties
  24. private ITimer _timer;
  25. public DeviceInfo Properties { get; set; }
  26. private int _muteVol;
  27. public bool IsMuted { get; set; }
  28. private int _volume;
  29. public int Volume
  30. {
  31. get
  32. {
  33. RefreshVolumeIfNeeded();
  34. return _volume;
  35. }
  36. set
  37. {
  38. _volume = value;
  39. }
  40. }
  41. public TimeSpan? Duration { get; set; }
  42. private TimeSpan _position = TimeSpan.FromSeconds(0);
  43. public TimeSpan Position
  44. {
  45. get
  46. {
  47. return _position;
  48. }
  49. set
  50. {
  51. _position = value;
  52. }
  53. }
  54. public TRANSPORTSTATE TransportState { get; private set; }
  55. public bool IsPlaying
  56. {
  57. get
  58. {
  59. return TransportState == TRANSPORTSTATE.PLAYING;
  60. }
  61. }
  62. public bool IsPaused
  63. {
  64. get
  65. {
  66. return TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK;
  67. }
  68. }
  69. public bool IsStopped
  70. {
  71. get
  72. {
  73. return TransportState == TRANSPORTSTATE.STOPPED;
  74. }
  75. }
  76. #endregion
  77. private readonly IHttpClient _httpClient;
  78. private readonly ILogger _logger;
  79. private readonly IServerConfigurationManager _config;
  80. public DateTime DateLastActivity { get; private set; }
  81. public Action OnDeviceUnavailable { get; set; }
  82. private readonly ITimerFactory _timerFactory;
  83. public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config, ITimerFactory timerFactory)
  84. {
  85. Properties = deviceProperties;
  86. _httpClient = httpClient;
  87. _logger = logger;
  88. _config = config;
  89. _timerFactory = timerFactory;
  90. }
  91. private int GetPlaybackTimerIntervalMs()
  92. {
  93. return 1000;
  94. }
  95. private int GetInactiveTimerIntervalMs()
  96. {
  97. return 30000;
  98. }
  99. public void Start()
  100. {
  101. _timer = _timerFactory.Create(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
  102. _timerActive = false;
  103. }
  104. private DateTime _lastVolumeRefresh;
  105. private void RefreshVolumeIfNeeded()
  106. {
  107. if (!_timerActive)
  108. {
  109. return;
  110. }
  111. if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
  112. {
  113. _lastVolumeRefresh = DateTime.UtcNow;
  114. RefreshVolume();
  115. }
  116. }
  117. private async void RefreshVolume()
  118. {
  119. if (_disposed)
  120. return;
  121. try
  122. {
  123. await GetVolume().ConfigureAwait(false);
  124. await GetMute().ConfigureAwait(false);
  125. }
  126. catch (Exception ex)
  127. {
  128. _logger.ErrorException("Error updating device volume info for {0}", ex, Properties.Name);
  129. }
  130. }
  131. private readonly object _timerLock = new object();
  132. private bool _timerActive;
  133. private void RestartTimer()
  134. {
  135. if (_disposed)
  136. return;
  137. if (!_timerActive)
  138. {
  139. lock (_timerLock)
  140. {
  141. if (!_timerActive)
  142. {
  143. _logger.Debug("RestartTimer");
  144. _timer.Change(10, GetPlaybackTimerIntervalMs());
  145. }
  146. _timerActive = true;
  147. }
  148. }
  149. }
  150. /// <summary>
  151. /// Restarts the timer in inactive mode.
  152. /// </summary>
  153. private void RestartTimerInactive()
  154. {
  155. if (_disposed)
  156. return;
  157. if (_timerActive)
  158. {
  159. lock (_timerLock)
  160. {
  161. if (_timerActive)
  162. {
  163. _logger.Debug("RestartTimerInactive");
  164. var interval = GetInactiveTimerIntervalMs();
  165. if (_timer != null)
  166. {
  167. _timer.Change(interval, interval);
  168. }
  169. }
  170. _timerActive = false;
  171. }
  172. }
  173. }
  174. #region Commanding
  175. public Task VolumeDown()
  176. {
  177. var sendVolume = Math.Max(Volume - 5, 0);
  178. return SetVolume(sendVolume);
  179. }
  180. public Task VolumeUp()
  181. {
  182. var sendVolume = Math.Min(Volume + 5, 100);
  183. return SetVolume(sendVolume);
  184. }
  185. public Task ToggleMute()
  186. {
  187. if (IsMuted)
  188. {
  189. return Unmute();
  190. }
  191. return Mute();
  192. }
  193. public async Task Mute()
  194. {
  195. var success = await SetMute(true).ConfigureAwait(true);
  196. if (!success)
  197. {
  198. await SetVolume(0).ConfigureAwait(false);
  199. }
  200. }
  201. public async Task Unmute()
  202. {
  203. var success = await SetMute(false).ConfigureAwait(true);
  204. if (!success)
  205. {
  206. var sendVolume = _muteVol <= 0 ? 20 : _muteVol;
  207. await SetVolume(sendVolume).ConfigureAwait(false);
  208. }
  209. }
  210. private DeviceService GetServiceRenderingControl()
  211. {
  212. var services = Properties.Services;
  213. return services.FirstOrDefault(s => string.Equals(s.ServiceType, "urn:schemas-upnp-org:service:RenderingControl:1", StringComparison.OrdinalIgnoreCase)) ??
  214. services.FirstOrDefault(s => (s.ServiceType ?? string.Empty).StartsWith("urn:schemas-upnp-org:service:RenderingControl", StringComparison.OrdinalIgnoreCase));
  215. }
  216. private DeviceService GetAvTransportService()
  217. {
  218. var services = Properties.Services;
  219. return services.FirstOrDefault(s => string.Equals(s.ServiceType, "urn:schemas-upnp-org:service:AVTransport:1", StringComparison.OrdinalIgnoreCase)) ??
  220. services.FirstOrDefault(s => (s.ServiceType ?? string.Empty).StartsWith("urn:schemas-upnp-org:service:AVTransport", StringComparison.OrdinalIgnoreCase));
  221. }
  222. private async Task<bool> SetMute(bool mute)
  223. {
  224. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
  225. if (command == null)
  226. return false;
  227. var service = GetServiceRenderingControl();
  228. if (service == null)
  229. {
  230. return false;
  231. }
  232. _logger.Debug("Setting mute");
  233. var value = mute ? 1 : 0;
  234. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
  235. .ConfigureAwait(false);
  236. IsMuted = mute;
  237. return true;
  238. }
  239. /// <summary>
  240. /// Sets volume on a scale of 0-100
  241. /// </summary>
  242. public async Task SetVolume(int value)
  243. {
  244. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
  245. if (command == null)
  246. return;
  247. var service = GetServiceRenderingControl();
  248. if (service == null)
  249. {
  250. throw new InvalidOperationException("Unable to find service");
  251. }
  252. // Set it early and assume it will succeed
  253. // Remote control will perform better
  254. Volume = value;
  255. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, value))
  256. .ConfigureAwait(false);
  257. }
  258. public async Task Seek(TimeSpan value)
  259. {
  260. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
  261. if (command == null)
  262. return;
  263. var service = GetAvTransportService();
  264. if (service == null)
  265. {
  266. throw new InvalidOperationException("Unable to find service");
  267. }
  268. 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"))
  269. .ConfigureAwait(false);
  270. }
  271. public async Task SetAvTransport(string url, string header, string metaData)
  272. {
  273. _logger.Debug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
  274. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
  275. if (command == null)
  276. return;
  277. var dictionary = new Dictionary<string, string>
  278. {
  279. {"CurrentURI", url},
  280. {"CurrentURIMetaData", CreateDidlMeta(metaData)}
  281. };
  282. var service = GetAvTransportService();
  283. if (service == null)
  284. {
  285. throw new InvalidOperationException("Unable to find service");
  286. }
  287. var post = AvCommands.BuildPost(command, service.ServiceType, url, dictionary);
  288. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
  289. .ConfigureAwait(false);
  290. await Task.Delay(50).ConfigureAwait(false);
  291. try
  292. {
  293. await SetPlay().ConfigureAwait(false);
  294. }
  295. catch
  296. {
  297. // Some devices will throw an error if you tell it to play when it's already playing
  298. // Others won't
  299. }
  300. RestartTimer();
  301. }
  302. private string CreateDidlMeta(string value)
  303. {
  304. if (string.IsNullOrEmpty(value))
  305. return String.Empty;
  306. return DescriptionXmlBuilder.Escape(value);
  307. }
  308. public async Task SetPlay()
  309. {
  310. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
  311. if (command == null)
  312. return;
  313. var service = GetAvTransportService();
  314. if (service == null)
  315. {
  316. throw new InvalidOperationException("Unable to find service");
  317. }
  318. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
  319. .ConfigureAwait(false);
  320. }
  321. public async Task SetStop()
  322. {
  323. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
  324. if (command == null)
  325. return;
  326. var service = GetAvTransportService();
  327. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
  328. .ConfigureAwait(false);
  329. }
  330. public async Task SetPause()
  331. {
  332. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
  333. if (command == null)
  334. return;
  335. var service = GetAvTransportService();
  336. await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
  337. .ConfigureAwait(false);
  338. TransportState = TRANSPORTSTATE.PAUSED;
  339. }
  340. #endregion
  341. #region Get data
  342. private int _successiveStopCount;
  343. private int _connectFailureCount;
  344. private async void TimerCallback(object sender)
  345. {
  346. if (_disposed)
  347. return;
  348. const int maxSuccessiveStopReturns = 5;
  349. try
  350. {
  351. var transportState = await GetTransportInfo().ConfigureAwait(false);
  352. if (_disposed)
  353. {
  354. return;
  355. }
  356. DateLastActivity = DateTime.UtcNow;
  357. if (transportState.HasValue)
  358. {
  359. // If we're not playing anything no need to get additional data
  360. if (transportState.Value == TRANSPORTSTATE.STOPPED)
  361. {
  362. UpdateMediaInfo(null, transportState.Value);
  363. }
  364. else
  365. {
  366. var tuple = await GetPositionInfo().ConfigureAwait(false);
  367. var currentObject = tuple.Item2;
  368. if (tuple.Item1 && currentObject == null)
  369. {
  370. currentObject = await GetMediaInfo().ConfigureAwait(false);
  371. }
  372. if (currentObject != null)
  373. {
  374. UpdateMediaInfo(currentObject, transportState.Value);
  375. }
  376. }
  377. _connectFailureCount = 0;
  378. if (_disposed)
  379. return;
  380. // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
  381. if (transportState.Value == TRANSPORTSTATE.STOPPED)
  382. {
  383. _successiveStopCount++;
  384. if (_successiveStopCount >= maxSuccessiveStopReturns)
  385. {
  386. RestartTimerInactive();
  387. }
  388. }
  389. else
  390. {
  391. _successiveStopCount = 0;
  392. RestartTimer();
  393. }
  394. }
  395. }
  396. catch (HttpException ex)
  397. {
  398. if (_disposed)
  399. return;
  400. //_logger.ErrorException("Error updating device info for {0}", ex, Properties.Name);
  401. _successiveStopCount++;
  402. _connectFailureCount++;
  403. if (_connectFailureCount >= 3)
  404. {
  405. if (OnDeviceUnavailable != null)
  406. {
  407. _logger.Debug("Disposing device due to loss of connection");
  408. OnDeviceUnavailable();
  409. return;
  410. }
  411. }
  412. if (_successiveStopCount >= maxSuccessiveStopReturns)
  413. {
  414. RestartTimerInactive();
  415. }
  416. }
  417. catch (Exception ex)
  418. {
  419. if (_disposed)
  420. return;
  421. _logger.ErrorException("Error updating device info for {0}", ex, Properties.Name);
  422. _successiveStopCount++;
  423. if (_successiveStopCount >= maxSuccessiveStopReturns)
  424. {
  425. RestartTimerInactive();
  426. }
  427. }
  428. }
  429. private async Task GetVolume()
  430. {
  431. if (_disposed)
  432. {
  433. return;
  434. }
  435. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
  436. if (command == null)
  437. return;
  438. var service = GetServiceRenderingControl();
  439. if (service == null)
  440. {
  441. return;
  442. }
  443. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), true)
  444. .ConfigureAwait(false);
  445. if (result == null || result.Document == null)
  446. return;
  447. var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
  448. var volumeValue = volume == null ? null : volume.Value;
  449. if (string.IsNullOrWhiteSpace(volumeValue))
  450. return;
  451. Volume = int.Parse(volumeValue, UsCulture);
  452. if (Volume > 0)
  453. {
  454. _muteVol = Volume;
  455. }
  456. }
  457. private async Task GetMute()
  458. {
  459. if (_disposed)
  460. {
  461. return;
  462. }
  463. var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
  464. if (command == null)
  465. return;
  466. var service = GetServiceRenderingControl();
  467. if (service == null)
  468. {
  469. return;
  470. }
  471. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), true)
  472. .ConfigureAwait(false);
  473. if (result == null || result.Document == null)
  474. return;
  475. var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse").Select(i => i.Element("CurrentMute")).FirstOrDefault(i => i != null);
  476. var value = valueNode == null ? null : valueNode.Value;
  477. IsMuted = string.Equals(value, "1", StringComparison.OrdinalIgnoreCase);
  478. }
  479. private async Task<TRANSPORTSTATE?> GetTransportInfo()
  480. {
  481. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
  482. if (command == null)
  483. return null;
  484. var service = GetAvTransportService();
  485. if (service == null)
  486. return null;
  487. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType), false)
  488. .ConfigureAwait(false);
  489. if (result == null || result.Document == null)
  490. return null;
  491. var transportState =
  492. result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
  493. var transportStateValue = transportState == null ? null : transportState.Value;
  494. if (transportStateValue != null)
  495. {
  496. TRANSPORTSTATE state;
  497. if (Enum.TryParse(transportStateValue, true, out state))
  498. {
  499. return state;
  500. }
  501. }
  502. return null;
  503. }
  504. private async Task<uBaseObject> GetMediaInfo()
  505. {
  506. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
  507. if (command == null)
  508. return null;
  509. var service = GetAvTransportService();
  510. if (service == null)
  511. {
  512. throw new InvalidOperationException("Unable to find service");
  513. }
  514. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), false)
  515. .ConfigureAwait(false);
  516. if (result == null || result.Document == null)
  517. return null;
  518. var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
  519. if (track == null)
  520. {
  521. return null;
  522. }
  523. var e = track.Element(uPnpNamespaces.items) ?? track;
  524. return UpnpContainer.Create(e);
  525. }
  526. private async Task<Tuple<bool, uBaseObject>> GetPositionInfo()
  527. {
  528. var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
  529. if (command == null)
  530. return new Tuple<bool, uBaseObject>(false, null);
  531. var service = GetAvTransportService();
  532. if (service == null)
  533. {
  534. throw new InvalidOperationException("Unable to find service");
  535. }
  536. var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType), false)
  537. .ConfigureAwait(false);
  538. if (result == null || result.Document == null)
  539. return new Tuple<bool, uBaseObject>(false, null);
  540. var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
  541. var trackUri = trackUriElem == null ? null : trackUriElem.Value;
  542. var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
  543. var duration = durationElem == null ? null : durationElem.Value;
  544. if (!string.IsNullOrWhiteSpace(duration) &&
  545. !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
  546. {
  547. Duration = TimeSpan.Parse(duration, UsCulture);
  548. }
  549. else
  550. {
  551. Duration = null;
  552. }
  553. var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
  554. var position = positionElem == null ? null : positionElem.Value;
  555. if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
  556. {
  557. Position = TimeSpan.Parse(position, UsCulture);
  558. }
  559. var track = result.Document.Descendants("TrackMetaData").FirstOrDefault();
  560. if (track == null)
  561. {
  562. //If track is null, some vendors do this, use GetMediaInfo instead
  563. return new Tuple<bool, uBaseObject>(true, null);
  564. }
  565. var trackString = (string)track;
  566. if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
  567. {
  568. return new Tuple<bool, uBaseObject>(false, null);
  569. }
  570. XElement uPnpResponse;
  571. // Handle different variations sent back by devices
  572. try
  573. {
  574. uPnpResponse = XElement.Parse(trackString);
  575. }
  576. catch (Exception)
  577. {
  578. // first try to add a root node with a dlna namesapce
  579. try
  580. {
  581. uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>");
  582. uPnpResponse = uPnpResponse.Descendants().First();
  583. }
  584. catch (Exception ex)
  585. {
  586. _logger.ErrorException("Unable to parse xml {0}", ex, trackString);
  587. return new Tuple<bool, uBaseObject>(true, null);
  588. }
  589. }
  590. var e = uPnpResponse.Element(uPnpNamespaces.items);
  591. var uTrack = CreateUBaseObject(e, trackUri);
  592. return new Tuple<bool, uBaseObject>(true, uTrack);
  593. }
  594. private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
  595. {
  596. if (container == null)
  597. {
  598. throw new ArgumentNullException("container");
  599. }
  600. var url = container.GetValue(uPnpNamespaces.Res);
  601. if (string.IsNullOrWhiteSpace(url))
  602. {
  603. url = trackUri;
  604. }
  605. return new uBaseObject
  606. {
  607. Id = container.GetAttributeValue(uPnpNamespaces.Id),
  608. ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
  609. Title = container.GetValue(uPnpNamespaces.title),
  610. IconUrl = container.GetValue(uPnpNamespaces.Artwork),
  611. SecondText = "",
  612. Url = url,
  613. ProtocolInfo = GetProtocolInfo(container),
  614. MetaData = container.ToString()
  615. };
  616. }
  617. private static string[] GetProtocolInfo(XElement container)
  618. {
  619. if (container == null)
  620. {
  621. throw new ArgumentNullException("container");
  622. }
  623. var resElement = container.Element(uPnpNamespaces.Res);
  624. if (resElement != null)
  625. {
  626. var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo);
  627. if (info != null && !string.IsNullOrWhiteSpace(info.Value))
  628. {
  629. return info.Value.Split(':');
  630. }
  631. }
  632. return new string[4];
  633. }
  634. #endregion
  635. #region From XML
  636. private async Task GetAVProtocolAsync()
  637. {
  638. if (_disposed)
  639. {
  640. return;
  641. }
  642. var avService = GetAvTransportService();
  643. if (avService == null)
  644. return;
  645. string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
  646. var httpClient = new SsdpHttpClient(_httpClient, _config);
  647. var document = await httpClient.GetDataAsync(url).ConfigureAwait(false);
  648. AvCommands = TransportCommands.Create(document);
  649. }
  650. private async Task GetRenderingProtocolAsync()
  651. {
  652. if (_disposed)
  653. {
  654. return;
  655. }
  656. var avService = GetServiceRenderingControl();
  657. if (avService == null)
  658. return;
  659. string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
  660. var httpClient = new SsdpHttpClient(_httpClient, _config);
  661. var document = await httpClient.GetDataAsync(url).ConfigureAwait(false);
  662. RendererCommands = TransportCommands.Create(document);
  663. }
  664. private string NormalizeUrl(string baseUrl, string url)
  665. {
  666. // If it's already a complete url, don't stick anything onto the front of it
  667. if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
  668. {
  669. return url;
  670. }
  671. if (!url.Contains("/"))
  672. url = "/dmr/" + url;
  673. if (!url.StartsWith("/"))
  674. url = "/" + url;
  675. return baseUrl + url;
  676. }
  677. private TransportCommands AvCommands
  678. {
  679. get;
  680. set;
  681. }
  682. internal TransportCommands RendererCommands
  683. {
  684. get;
  685. set;
  686. }
  687. public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, ITimerFactory timerFactory)
  688. {
  689. var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
  690. var document = await ssdpHttpClient.GetDataAsync(url.ToString()).ConfigureAwait(false);
  691. var deviceProperties = new DeviceInfo();
  692. var friendlyNames = new List<string>();
  693. var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
  694. if (name != null && !string.IsNullOrWhiteSpace(name.Value))
  695. friendlyNames.Add(name.Value);
  696. var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
  697. if (room != null && !string.IsNullOrWhiteSpace(room.Value))
  698. friendlyNames.Add(room.Value);
  699. deviceProperties.Name = string.Join(" ", friendlyNames.ToArray(friendlyNames.Count));
  700. var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
  701. if (model != null)
  702. deviceProperties.ModelName = model.Value;
  703. var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
  704. if (modelNumber != null)
  705. deviceProperties.ModelNumber = modelNumber.Value;
  706. var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
  707. if (uuid != null)
  708. deviceProperties.UUID = uuid.Value;
  709. var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
  710. if (manufacturer != null)
  711. deviceProperties.Manufacturer = manufacturer.Value;
  712. var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
  713. if (manufacturerUrl != null)
  714. deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
  715. var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
  716. if (presentationUrl != null)
  717. deviceProperties.PresentationUrl = presentationUrl.Value;
  718. var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
  719. if (modelUrl != null)
  720. deviceProperties.ModelUrl = modelUrl.Value;
  721. var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
  722. if (serialNumber != null)
  723. deviceProperties.SerialNumber = serialNumber.Value;
  724. var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
  725. if (modelDescription != null)
  726. deviceProperties.ModelDescription = modelDescription.Value;
  727. deviceProperties.BaseUrl = String.Format("http://{0}:{1}", url.Host, url.Port);
  728. var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
  729. if (icon != null)
  730. {
  731. deviceProperties.Icon = CreateIcon(icon);
  732. }
  733. foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
  734. {
  735. if (services == null)
  736. continue;
  737. var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
  738. if (servicesList == null)
  739. continue;
  740. foreach (var element in servicesList)
  741. {
  742. var service = Create(element);
  743. if (service != null)
  744. {
  745. deviceProperties.Services.Add(service);
  746. }
  747. }
  748. }
  749. var device = new Device(deviceProperties, httpClient, logger, config, timerFactory);
  750. if (device.GetAvTransportService() != null)
  751. {
  752. await device.GetRenderingProtocolAsync().ConfigureAwait(false);
  753. await device.GetAVProtocolAsync().ConfigureAwait(false);
  754. }
  755. return device;
  756. }
  757. #endregion
  758. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  759. private static DeviceIcon CreateIcon(XElement element)
  760. {
  761. if (element == null)
  762. {
  763. throw new ArgumentNullException("element");
  764. }
  765. var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype"));
  766. var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width"));
  767. var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height"));
  768. var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth"));
  769. var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url"));
  770. var widthValue = int.Parse(width, NumberStyles.Any, UsCulture);
  771. var heightValue = int.Parse(height, NumberStyles.Any, UsCulture);
  772. return new DeviceIcon
  773. {
  774. Depth = depth,
  775. Height = heightValue,
  776. MimeType = mimeType,
  777. Url = url,
  778. Width = widthValue
  779. };
  780. }
  781. private static DeviceService Create(XElement element)
  782. {
  783. var type = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceType"));
  784. var id = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceId"));
  785. var scpdUrl = element.GetDescendantValue(uPnpNamespaces.ud.GetName("SCPDURL"));
  786. var controlURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("controlURL"));
  787. var eventSubURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("eventSubURL"));
  788. return new DeviceService
  789. {
  790. ControlUrl = controlURL,
  791. EventSubUrl = eventSubURL,
  792. ScpdUrl = scpdUrl,
  793. ServiceId = id,
  794. ServiceType = type
  795. };
  796. }
  797. public event EventHandler<PlaybackStartEventArgs> PlaybackStart;
  798. public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
  799. public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped;
  800. public event EventHandler<MediaChangedEventArgs> MediaChanged;
  801. public uBaseObject CurrentMediaInfo { get; private set; }
  802. private void UpdateMediaInfo(uBaseObject mediaInfo, TRANSPORTSTATE state)
  803. {
  804. TransportState = state;
  805. var previousMediaInfo = CurrentMediaInfo;
  806. CurrentMediaInfo = mediaInfo;
  807. if (previousMediaInfo == null && mediaInfo != null)
  808. {
  809. if (state != TRANSPORTSTATE.STOPPED)
  810. {
  811. OnPlaybackStart(mediaInfo);
  812. }
  813. }
  814. else if (mediaInfo != null && previousMediaInfo != null && !mediaInfo.Equals(previousMediaInfo))
  815. {
  816. OnMediaChanged(previousMediaInfo, mediaInfo);
  817. }
  818. else if (mediaInfo == null && previousMediaInfo != null)
  819. {
  820. OnPlaybackStop(previousMediaInfo);
  821. }
  822. else if (mediaInfo != null && mediaInfo.Equals(previousMediaInfo))
  823. {
  824. OnPlaybackProgress(mediaInfo);
  825. }
  826. }
  827. private void OnPlaybackStart(uBaseObject mediaInfo)
  828. {
  829. if (PlaybackStart != null)
  830. {
  831. PlaybackStart.Invoke(this, new PlaybackStartEventArgs
  832. {
  833. MediaInfo = mediaInfo
  834. });
  835. }
  836. }
  837. private void OnPlaybackProgress(uBaseObject mediaInfo)
  838. {
  839. if (PlaybackProgress != null)
  840. {
  841. PlaybackProgress.Invoke(this, new PlaybackProgressEventArgs
  842. {
  843. MediaInfo = mediaInfo
  844. });
  845. }
  846. }
  847. private void OnPlaybackStop(uBaseObject mediaInfo)
  848. {
  849. if (PlaybackStopped != null)
  850. {
  851. PlaybackStopped.Invoke(this, new PlaybackStoppedEventArgs
  852. {
  853. MediaInfo = mediaInfo
  854. });
  855. }
  856. }
  857. private void OnMediaChanged(uBaseObject old, uBaseObject newMedia)
  858. {
  859. if (MediaChanged != null)
  860. {
  861. MediaChanged.Invoke(this, new MediaChangedEventArgs
  862. {
  863. OldMediaInfo = old,
  864. NewMediaInfo = newMedia
  865. });
  866. }
  867. }
  868. #region IDisposable
  869. bool _disposed;
  870. public void Dispose()
  871. {
  872. if (!_disposed)
  873. {
  874. _disposed = true;
  875. DisposeTimer();
  876. }
  877. }
  878. private void DisposeTimer()
  879. {
  880. if (_timer != null)
  881. {
  882. _timer.Dispose();
  883. _timer = null;
  884. }
  885. }
  886. #endregion
  887. public override string ToString()
  888. {
  889. return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
  890. }
  891. }
  892. }