DlnaController.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller;
  3. using MediaBrowser.Controller.Dlna;
  4. using MediaBrowser.Controller.Entities;
  5. using MediaBrowser.Controller.Entities.Audio;
  6. using MediaBrowser.Controller.Library;
  7. using MediaBrowser.Controller.Persistence;
  8. using MediaBrowser.Controller.Session;
  9. using MediaBrowser.Model.Entities;
  10. using MediaBrowser.Model.Logging;
  11. using MediaBrowser.Model.Session;
  12. using System;
  13. using System.Collections.Generic;
  14. using System.Linq;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. namespace MediaBrowser.Dlna.PlayTo
  18. {
  19. public class PlayToController : ISessionController, IDisposable
  20. {
  21. private Device _device;
  22. private BaseItem _currentItem;
  23. private readonly SessionInfo _session;
  24. private readonly ISessionManager _sessionManager;
  25. private readonly IItemRepository _itemRepository;
  26. private readonly ILibraryManager _libraryManager;
  27. private readonly INetworkManager _networkManager;
  28. private readonly ILogger _logger;
  29. private readonly IDlnaManager _dlnaManager;
  30. private readonly IUserManager _userManager;
  31. private readonly IServerApplicationHost _appHost;
  32. private bool _playbackStarted;
  33. private const int UpdateTimerIntervalMs = 1000;
  34. public bool SupportsMediaRemoteControl
  35. {
  36. get { return true; }
  37. }
  38. public bool IsSessionActive
  39. {
  40. get
  41. {
  42. if (_device == null || _device.UpdateTime == default(DateTime))
  43. return false;
  44. return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
  45. }
  46. }
  47. public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost)
  48. {
  49. _session = session;
  50. _itemRepository = itemRepository;
  51. _sessionManager = sessionManager;
  52. _libraryManager = libraryManager;
  53. _networkManager = networkManager;
  54. _dlnaManager = dlnaManager;
  55. _userManager = userManager;
  56. _appHost = appHost;
  57. _logger = logger;
  58. }
  59. public void Init(Device device)
  60. {
  61. _device = device;
  62. _device.PlaybackChanged += Device_PlaybackChanged;
  63. _device.CurrentIdChanged += Device_CurrentIdChanged;
  64. _device.Start();
  65. _updateTimer = new Timer(updateTimer_Elapsed, null, UpdateTimerIntervalMs, UpdateTimerIntervalMs);
  66. }
  67. #region Device EventHandlers & Update Timer
  68. Timer _updateTimer;
  69. async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
  70. {
  71. if (_currentItem == null)
  72. return;
  73. if (e.Stopped == false)
  74. await ReportProgress().ConfigureAwait(false);
  75. else if (e.Stopped && _playbackStarted)
  76. {
  77. _playbackStarted = false;
  78. await _sessionManager.OnPlaybackStopped(new Controller.Session.PlaybackStopInfo
  79. {
  80. Item = _currentItem,
  81. SessionId = _session.Id,
  82. PositionTicks = _device.Position.Ticks
  83. }).ConfigureAwait(false);
  84. await SetNext().ConfigureAwait(false);
  85. }
  86. }
  87. async void Device_CurrentIdChanged(object sender, CurrentIdEventArgs e)
  88. {
  89. if (!string.IsNullOrWhiteSpace(e.Id))
  90. {
  91. Guid guid;
  92. if (Guid.TryParse(e.Id, out guid))
  93. {
  94. if (_currentItem != null && _currentItem.Id == guid)
  95. {
  96. return;
  97. }
  98. var item = _libraryManager.GetItemById(guid);
  99. if (item != null)
  100. {
  101. _logger.Debug("{0} - CurrentId {1}", _session.DeviceName, item.Id);
  102. _currentItem = item;
  103. _playbackStarted = false;
  104. await ReportProgress().ConfigureAwait(false);
  105. }
  106. }
  107. }
  108. }
  109. /// <summary>
  110. /// Handles the Elapsed event of the updateTimer control.
  111. /// </summary>
  112. /// <param name="state">The state.</param>
  113. private async void updateTimer_Elapsed(object state)
  114. {
  115. if (_disposed)
  116. return;
  117. if (IsSessionActive)
  118. {
  119. await ReportProgress().ConfigureAwait(false);
  120. }
  121. else
  122. {
  123. _updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
  124. try
  125. {
  126. // Session is inactive, mark it for Disposal and don't start the elapsed timer.
  127. await _sessionManager.ReportSessionEnded(_session.Id);
  128. }
  129. catch (Exception ex)
  130. {
  131. _logger.ErrorException("Error in ReportSessionEnded", ex);
  132. }
  133. }
  134. }
  135. /// <summary>
  136. /// Reports the playback progress.
  137. /// </summary>
  138. /// <returns></returns>
  139. private async Task ReportProgress()
  140. {
  141. if (_currentItem == null || _device.IsStopped)
  142. return;
  143. if (!_playbackStarted)
  144. {
  145. await _sessionManager.OnPlaybackStart(new PlaybackInfo
  146. {
  147. Item = _currentItem,
  148. SessionId = _session.Id,
  149. CanSeek = true,
  150. QueueableMediaTypes = new List<string> { _currentItem.MediaType }
  151. }).ConfigureAwait(false);
  152. _playbackStarted = true;
  153. }
  154. if ((_device.IsPlaying || _device.IsPaused))
  155. {
  156. var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
  157. if (playlistItem != null && playlistItem.Transcode)
  158. {
  159. await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
  160. {
  161. Item = _currentItem,
  162. SessionId = _session.Id,
  163. PositionTicks = _device.Position.Ticks + playlistItem.StartPositionTicks,
  164. IsMuted = _device.IsMuted,
  165. IsPaused = _device.IsPaused
  166. }).ConfigureAwait(false);
  167. }
  168. else if (_currentItem != null)
  169. {
  170. await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
  171. {
  172. Item = _currentItem,
  173. SessionId = _session.Id,
  174. PositionTicks = _device.Position.Ticks,
  175. IsMuted = _device.IsMuted,
  176. IsPaused = _device.IsPaused
  177. }).ConfigureAwait(false);
  178. }
  179. }
  180. }
  181. #endregion
  182. #region SendCommands
  183. public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
  184. {
  185. _logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
  186. var items = new List<BaseItem>();
  187. foreach (string id in command.ItemIds)
  188. {
  189. AddItemFromId(Guid.Parse(id), items);
  190. }
  191. var playlist = new List<PlaylistItem>();
  192. var isFirst = true;
  193. var serverAddress = GetServerAddress();
  194. foreach (var item in items)
  195. {
  196. if (isFirst && command.StartPositionTicks.HasValue)
  197. {
  198. playlist.Add(CreatePlaylistItem(item, command.StartPositionTicks.Value, serverAddress));
  199. isFirst = false;
  200. }
  201. else
  202. {
  203. playlist.Add(CreatePlaylistItem(item, 0, serverAddress));
  204. }
  205. }
  206. _logger.Debug("{0} - Playlist created", _session.DeviceName);
  207. if (command.PlayCommand == PlayCommand.PlayLast)
  208. {
  209. AddItemsToPlaylist(playlist);
  210. }
  211. if (command.PlayCommand == PlayCommand.PlayNext)
  212. {
  213. AddItemsToPlaylist(playlist);
  214. }
  215. _logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
  216. if (!string.IsNullOrWhiteSpace(command.ControllingUserId))
  217. {
  218. var userId = new Guid(command.ControllingUserId);
  219. var user = _userManager.GetUserById(userId);
  220. await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
  221. _session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false);
  222. }
  223. await PlayItems(playlist).ConfigureAwait(false);
  224. }
  225. public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
  226. {
  227. switch (command.Command)
  228. {
  229. case PlaystateCommand.Stop:
  230. Playlist.Clear();
  231. return _device.SetStop();
  232. case PlaystateCommand.Pause:
  233. return _device.SetPause();
  234. case PlaystateCommand.Unpause:
  235. return _device.SetPlay();
  236. case PlaystateCommand.Seek:
  237. var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
  238. if (playlistItem != null && playlistItem.Transcode && playlistItem.MediaType == DlnaProfileType.Video && _currentItem != null)
  239. {
  240. var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
  241. playlistItem.StartPositionTicks = newItem.StartPositionTicks;
  242. playlistItem.StreamUrl = newItem.StreamUrl;
  243. playlistItem.Didl = newItem.Didl;
  244. return _device.SetAvTransport(playlistItem.StreamUrl, GetDlnaHeaders(playlistItem), playlistItem.Didl);
  245. }
  246. return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
  247. case PlaystateCommand.NextTrack:
  248. _currentItem = null;
  249. return SetNext();
  250. case PlaystateCommand.PreviousTrack:
  251. _currentItem = null;
  252. return SetPrevious();
  253. }
  254. return Task.FromResult(true);
  255. }
  256. public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken)
  257. {
  258. switch (command)
  259. {
  260. case SystemCommand.VolumeDown:
  261. return _device.VolumeDown();
  262. case SystemCommand.VolumeUp:
  263. return _device.VolumeUp();
  264. case SystemCommand.Mute:
  265. return _device.VolumeDown(true);
  266. case SystemCommand.Unmute:
  267. return _device.VolumeUp(true);
  268. case SystemCommand.ToggleMute:
  269. return _device.ToggleMute();
  270. default:
  271. return Task.FromResult(true);
  272. }
  273. }
  274. public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
  275. {
  276. return Task.FromResult(true);
  277. }
  278. public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
  279. {
  280. return Task.FromResult(true);
  281. }
  282. public Task SendServerRestartNotification(CancellationToken cancellationToken)
  283. {
  284. return Task.FromResult(true);
  285. }
  286. public Task SendServerShutdownNotification(CancellationToken cancellationToken)
  287. {
  288. return Task.FromResult(true);
  289. }
  290. public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
  291. {
  292. return Task.FromResult(true);
  293. }
  294. public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
  295. {
  296. return Task.FromResult(true);
  297. }
  298. public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
  299. {
  300. return Task.FromResult(true);
  301. }
  302. #endregion
  303. #region Playlist
  304. private List<PlaylistItem> _playlist = new List<PlaylistItem>();
  305. private List<PlaylistItem> Playlist
  306. {
  307. get
  308. {
  309. return _playlist;
  310. }
  311. set
  312. {
  313. _playlist = value;
  314. }
  315. }
  316. private void AddItemFromId(Guid id, List<BaseItem> list)
  317. {
  318. var item = _libraryManager.GetItemById(id);
  319. if (item.IsFolder)
  320. {
  321. foreach (var childId in _itemRepository.GetChildren(item.Id))
  322. {
  323. AddItemFromId(childId, list);
  324. }
  325. }
  326. else
  327. {
  328. if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
  329. {
  330. list.Add(item);
  331. }
  332. }
  333. }
  334. private string GetServerAddress()
  335. {
  336. return string.Format("{0}://{1}:{2}/mediabrowser",
  337. "http",
  338. _networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost",
  339. _appHost.HttpServerPort
  340. );
  341. }
  342. private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
  343. {
  344. var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery
  345. {
  346. ItemId = item.Id
  347. }).ToList();
  348. var deviceInfo = _device.Properties;
  349. var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
  350. _dlnaManager.GetDefaultProfile();
  351. var playlistItem = GetPlaylistItem(item, streams, profile);
  352. playlistItem.StartPositionTicks = startPostionTicks;
  353. playlistItem.DeviceProfileId = profile.Id;
  354. if (playlistItem.MediaType == DlnaProfileType.Audio)
  355. {
  356. playlistItem.StreamUrl = StreamHelper.GetAudioUrl(deviceInfo, playlistItem, streams, serverAddress);
  357. }
  358. else
  359. {
  360. playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
  361. }
  362. playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams, profile.EnableAlbumArtInDidl);
  363. return playlistItem;
  364. }
  365. private string GetDlnaHeaders(PlaylistItem item)
  366. {
  367. var orgOp = item.Transcode ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
  368. var orgCi = item.Transcode ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
  369. const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
  370. string contentFeatures;
  371. var container = item.Container.TrimStart('.');
  372. if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
  373. {
  374. contentFeatures = "DLNA.ORG_PN=MP3";
  375. }
  376. else if (string.Equals(container, "wma", StringComparison.OrdinalIgnoreCase))
  377. {
  378. contentFeatures = "DLNA.ORG_PN=WMABASE";
  379. }
  380. else if (string.Equals(container, "wmw", StringComparison.OrdinalIgnoreCase))
  381. {
  382. contentFeatures = "DLNA.ORG_PN=WMVMED_BASE";
  383. }
  384. else if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
  385. {
  386. contentFeatures = "DLNA.ORG_PN=WMVMED_BASE";
  387. }
  388. else if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase))
  389. {
  390. contentFeatures = "DLNA.ORG_PN=AVI";
  391. }
  392. else if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
  393. {
  394. contentFeatures = "DLNA.ORG_PN=MATROSKA";
  395. }
  396. else if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
  397. {
  398. contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC";
  399. }
  400. else if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
  401. {
  402. contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
  403. }
  404. else if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
  405. {
  406. contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
  407. }
  408. else if (item.MediaType == DlnaProfileType.Video)
  409. {
  410. // Default to AVI for video
  411. contentFeatures = "DLNA.ORG_PN=AVI";
  412. }
  413. else
  414. {
  415. // Default to MP3 for audio
  416. contentFeatures = "DLNA.ORG_PN=MP3";
  417. }
  418. return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
  419. }
  420. private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaStream> mediaStreams, DeviceProfile profile)
  421. {
  422. var video = item as Video;
  423. if (video != null)
  424. {
  425. return new PlaylistItemFactory().Create(video, mediaStreams, profile);
  426. }
  427. var audio = item as Audio;
  428. if (audio != null)
  429. {
  430. return new PlaylistItemFactory().Create(audio, mediaStreams, profile);
  431. }
  432. var photo = item as Photo;
  433. if (photo != null)
  434. {
  435. return new PlaylistItemFactory().Create(photo, profile);
  436. }
  437. throw new ArgumentException("Unrecognized item type.");
  438. }
  439. /// <summary>
  440. /// Plays the items.
  441. /// </summary>
  442. /// <param name="items">The items.</param>
  443. /// <returns></returns>
  444. private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
  445. {
  446. Playlist.Clear();
  447. Playlist.AddRange(items);
  448. await SetNext();
  449. return true;
  450. }
  451. /// <summary>
  452. /// Adds the items to playlist.
  453. /// </summary>
  454. /// <param name="items">The items.</param>
  455. private void AddItemsToPlaylist(IEnumerable<PlaylistItem> items)
  456. {
  457. Playlist.AddRange(items);
  458. }
  459. private async Task<bool> SetNext()
  460. {
  461. if (!Playlist.Any() || Playlist.All(i => i.PlayState != 0))
  462. {
  463. return true;
  464. }
  465. var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
  466. if (currentitem != null)
  467. {
  468. currentitem.PlayState = 2;
  469. }
  470. var nextTrack = Playlist.FirstOrDefault(i => i.PlayState == 0);
  471. if (nextTrack == null)
  472. {
  473. await _device.SetStop();
  474. return true;
  475. }
  476. nextTrack.PlayState = 1;
  477. var dlnaheaders = GetDlnaHeaders(nextTrack);
  478. _logger.Debug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", _device.Properties.Name, nextTrack.StreamUrl, dlnaheaders);
  479. await _device.SetAvTransport(nextTrack.StreamUrl, dlnaheaders, nextTrack.Didl);
  480. if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode)
  481. await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks));
  482. return true;
  483. }
  484. public Task<bool> SetPrevious()
  485. {
  486. if (!Playlist.Any() || Playlist.All(i => i.PlayState != 2))
  487. return Task.FromResult(false);
  488. var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
  489. var prevTrack = Playlist.LastOrDefault(i => i.PlayState == 2);
  490. if (currentitem != null)
  491. {
  492. currentitem.PlayState = 0;
  493. }
  494. if (prevTrack == null)
  495. return Task.FromResult(false);
  496. prevTrack.PlayState = 1;
  497. return _device.SetAvTransport(prevTrack.StreamUrl, GetDlnaHeaders(prevTrack), prevTrack.Didl);
  498. }
  499. #endregion
  500. private bool _disposed;
  501. public void Dispose()
  502. {
  503. if (!_disposed)
  504. {
  505. _disposed = true;
  506. _updateTimer.Dispose();
  507. _device.Dispose();
  508. _logger.Log(LogSeverity.Debug, "Controller disposed");
  509. }
  510. }
  511. public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken)
  512. {
  513. throw new NotImplementedException();
  514. }
  515. }
  516. }