SessionManager.cs 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746
  1. using MediaBrowser.Common.Events;
  2. using MediaBrowser.Common.Extensions;
  3. using MediaBrowser.Common.Net;
  4. using MediaBrowser.Controller;
  5. using MediaBrowser.Controller.Configuration;
  6. using MediaBrowser.Controller.Devices;
  7. using MediaBrowser.Controller.Drawing;
  8. using MediaBrowser.Controller.Dto;
  9. using MediaBrowser.Controller.Entities;
  10. using MediaBrowser.Controller.Entities.Audio;
  11. using MediaBrowser.Controller.Entities.TV;
  12. using MediaBrowser.Controller.Library;
  13. using MediaBrowser.Controller.LiveTv;
  14. using MediaBrowser.Controller.Persistence;
  15. using MediaBrowser.Controller.Security;
  16. using MediaBrowser.Controller.Session;
  17. using MediaBrowser.Model.Connect;
  18. using MediaBrowser.Model.Devices;
  19. using MediaBrowser.Model.Entities;
  20. using MediaBrowser.Model.Events;
  21. using MediaBrowser.Model.Library;
  22. using MediaBrowser.Model.Logging;
  23. using MediaBrowser.Model.Serialization;
  24. using MediaBrowser.Model.Session;
  25. using MediaBrowser.Model.Users;
  26. using MediaBrowser.Server.Implementations.Security;
  27. using System;
  28. using System.Collections.Concurrent;
  29. using System.Collections.Generic;
  30. using System.Globalization;
  31. using System.Linq;
  32. using System.Threading;
  33. using System.Threading.Tasks;
  34. namespace MediaBrowser.Server.Implementations.Session
  35. {
  36. /// <summary>
  37. /// Class SessionManager
  38. /// </summary>
  39. public class SessionManager : ISessionManager
  40. {
  41. /// <summary>
  42. /// The _user data repository
  43. /// </summary>
  44. private readonly IUserDataManager _userDataRepository;
  45. /// <summary>
  46. /// The _user repository
  47. /// </summary>
  48. private readonly IUserRepository _userRepository;
  49. /// <summary>
  50. /// The _logger
  51. /// </summary>
  52. private readonly ILogger _logger;
  53. private readonly ILibraryManager _libraryManager;
  54. private readonly IUserManager _userManager;
  55. private readonly IMusicManager _musicManager;
  56. private readonly IDtoService _dtoService;
  57. private readonly IImageProcessor _imageProcessor;
  58. private readonly IItemRepository _itemRepo;
  59. private readonly IHttpClient _httpClient;
  60. private readonly IJsonSerializer _jsonSerializer;
  61. private readonly IServerApplicationHost _appHost;
  62. private readonly IAuthenticationRepository _authRepo;
  63. private readonly IDeviceManager _deviceManager;
  64. /// <summary>
  65. /// Gets or sets the configuration manager.
  66. /// </summary>
  67. /// <value>The configuration manager.</value>
  68. private readonly IServerConfigurationManager _configurationManager;
  69. /// <summary>
  70. /// The _active connections
  71. /// </summary>
  72. private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections =
  73. new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase);
  74. public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed;
  75. public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationSucceeded;
  76. /// <summary>
  77. /// Occurs when [playback start].
  78. /// </summary>
  79. public event EventHandler<PlaybackProgressEventArgs> PlaybackStart;
  80. /// <summary>
  81. /// Occurs when [playback progress].
  82. /// </summary>
  83. public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
  84. /// <summary>
  85. /// Occurs when [playback stopped].
  86. /// </summary>
  87. public event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
  88. public event EventHandler<SessionEventArgs> SessionStarted;
  89. public event EventHandler<SessionEventArgs> CapabilitiesChanged;
  90. public event EventHandler<SessionEventArgs> SessionEnded;
  91. public event EventHandler<SessionEventArgs> SessionActivity;
  92. private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>();
  93. private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
  94. /// <summary>
  95. /// Initializes a new instance of the <see cref="SessionManager" /> class.
  96. /// </summary>
  97. /// <param name="userDataRepository">The user data repository.</param>
  98. /// <param name="configurationManager">The configuration manager.</param>
  99. /// <param name="logger">The logger.</param>
  100. /// <param name="userRepository">The user repository.</param>
  101. /// <param name="libraryManager">The library manager.</param>
  102. public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager)
  103. {
  104. _userDataRepository = userDataRepository;
  105. _configurationManager = configurationManager;
  106. _logger = logger;
  107. _userRepository = userRepository;
  108. _libraryManager = libraryManager;
  109. _userManager = userManager;
  110. _musicManager = musicManager;
  111. _dtoService = dtoService;
  112. _imageProcessor = imageProcessor;
  113. _itemRepo = itemRepo;
  114. _jsonSerializer = jsonSerializer;
  115. _appHost = appHost;
  116. _httpClient = httpClient;
  117. _authRepo = authRepo;
  118. _deviceManager = deviceManager;
  119. _deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated;
  120. }
  121. void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs<DeviceInfo> e)
  122. {
  123. foreach (var session in Sessions)
  124. {
  125. if (string.Equals(session.DeviceId, e.Argument.Id))
  126. {
  127. session.DeviceName = e.Argument.Name;
  128. }
  129. }
  130. }
  131. /// <summary>
  132. /// Adds the parts.
  133. /// </summary>
  134. /// <param name="sessionFactories">The session factories.</param>
  135. public void AddParts(IEnumerable<ISessionControllerFactory> sessionFactories)
  136. {
  137. _sessionFactories = sessionFactories.ToList();
  138. }
  139. /// <summary>
  140. /// Gets all connections.
  141. /// </summary>
  142. /// <value>All connections.</value>
  143. public IEnumerable<SessionInfo> Sessions
  144. {
  145. get { return _activeConnections.Values.OrderByDescending(c => c.LastActivityDate).ToList(); }
  146. }
  147. private void OnSessionStarted(SessionInfo info)
  148. {
  149. EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs
  150. {
  151. SessionInfo = info
  152. }, _logger);
  153. if (!string.IsNullOrWhiteSpace(info.DeviceId))
  154. {
  155. var capabilities = GetSavedCapabilities(info.DeviceId);
  156. if (capabilities != null)
  157. {
  158. ReportCapabilities(info, capabilities, false);
  159. }
  160. }
  161. }
  162. private async void OnSessionEnded(SessionInfo info)
  163. {
  164. try
  165. {
  166. await SendSessionEndedNotification(info, CancellationToken.None).ConfigureAwait(false);
  167. }
  168. catch (Exception ex)
  169. {
  170. _logger.ErrorException("Error in SendSessionEndedNotification", ex);
  171. }
  172. EventHelper.QueueEventIfNotNull(SessionEnded, this, new SessionEventArgs
  173. {
  174. SessionInfo = info
  175. }, _logger);
  176. var disposable = info.SessionController as IDisposable;
  177. if (disposable != null)
  178. {
  179. _logger.Debug("Disposing session controller {0}", disposable.GetType().Name);
  180. try
  181. {
  182. disposable.Dispose();
  183. }
  184. catch (Exception ex)
  185. {
  186. _logger.ErrorException("Error disposing session controller", ex);
  187. }
  188. }
  189. }
  190. /// <summary>
  191. /// Logs the user activity.
  192. /// </summary>
  193. /// <param name="clientType">Type of the client.</param>
  194. /// <param name="appVersion">The app version.</param>
  195. /// <param name="deviceId">The device id.</param>
  196. /// <param name="deviceName">Name of the device.</param>
  197. /// <param name="remoteEndPoint">The remote end point.</param>
  198. /// <param name="user">The user.</param>
  199. /// <returns>Task.</returns>
  200. /// <exception cref="System.ArgumentNullException">user</exception>
  201. /// <exception cref="System.UnauthorizedAccessException"></exception>
  202. public async Task<SessionInfo> LogSessionActivity(string clientType,
  203. string appVersion,
  204. string deviceId,
  205. string deviceName,
  206. string remoteEndPoint,
  207. User user)
  208. {
  209. if (string.IsNullOrEmpty(clientType))
  210. {
  211. throw new ArgumentNullException("clientType");
  212. }
  213. if (string.IsNullOrEmpty(appVersion))
  214. {
  215. throw new ArgumentNullException("appVersion");
  216. }
  217. if (string.IsNullOrEmpty(deviceId))
  218. {
  219. throw new ArgumentNullException("deviceId");
  220. }
  221. if (string.IsNullOrEmpty(deviceName))
  222. {
  223. throw new ArgumentNullException("deviceName");
  224. }
  225. if (user != null && user.Configuration.IsDisabled)
  226. {
  227. throw new AuthenticationException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
  228. }
  229. var activityDate = DateTime.UtcNow;
  230. var userId = user == null ? (Guid?)null : user.Id;
  231. var username = user == null ? null : user.Name;
  232. var session = await GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, userId, username).ConfigureAwait(false);
  233. session.LastActivityDate = activityDate;
  234. if (user == null)
  235. {
  236. return session;
  237. }
  238. var lastActivityDate = user.LastActivityDate;
  239. user.LastActivityDate = activityDate;
  240. // Don't log in the db anymore frequently than 10 seconds
  241. if (lastActivityDate.HasValue && (activityDate - lastActivityDate.Value).TotalSeconds < 10)
  242. {
  243. return session;
  244. }
  245. // Save this directly. No need to fire off all the events for this.
  246. await _userRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
  247. EventHelper.FireEventIfNotNull(SessionActivity, this, new SessionEventArgs
  248. {
  249. SessionInfo = session
  250. }, _logger);
  251. return session;
  252. }
  253. public async void ReportSessionEnded(string sessionId)
  254. {
  255. await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
  256. try
  257. {
  258. var session = GetSession(sessionId, false);
  259. if (session != null)
  260. {
  261. var key = GetSessionKey(session.Client, session.ApplicationVersion, session.DeviceId);
  262. SessionInfo removed;
  263. if (_activeConnections.TryRemove(key, out removed))
  264. {
  265. OnSessionEnded(removed);
  266. }
  267. }
  268. }
  269. finally
  270. {
  271. _sessionLock.Release();
  272. }
  273. }
  274. /// <summary>
  275. /// Updates the now playing item id.
  276. /// </summary>
  277. /// <param name="session">The session.</param>
  278. /// <param name="info">The information.</param>
  279. /// <param name="libraryItem">The library item.</param>
  280. private void UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem)
  281. {
  282. var runtimeTicks = libraryItem == null ? null : libraryItem.RunTimeTicks;
  283. if (string.IsNullOrWhiteSpace(info.MediaSourceId))
  284. {
  285. info.MediaSourceId = info.ItemId;
  286. }
  287. if (!string.Equals(info.ItemId, info.MediaSourceId) &&
  288. !string.IsNullOrWhiteSpace(info.MediaSourceId))
  289. {
  290. runtimeTicks = _libraryManager.GetItemById(new Guid(info.MediaSourceId)).RunTimeTicks;
  291. }
  292. if (!string.IsNullOrWhiteSpace(info.ItemId) && libraryItem != null)
  293. {
  294. var current = session.NowPlayingItem;
  295. if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase))
  296. {
  297. info.Item = GetItemInfo(libraryItem, libraryItem, info.MediaSourceId);
  298. }
  299. else
  300. {
  301. info.Item = current;
  302. }
  303. info.Item.RunTimeTicks = runtimeTicks;
  304. }
  305. session.NowPlayingItem = info.Item;
  306. session.LastActivityDate = DateTime.UtcNow;
  307. session.LastPlaybackCheckIn = DateTime.UtcNow;
  308. session.PlayState.IsPaused = info.IsPaused;
  309. session.PlayState.PositionTicks = info.PositionTicks;
  310. session.PlayState.MediaSourceId = info.MediaSourceId;
  311. session.PlayState.CanSeek = info.CanSeek;
  312. session.PlayState.IsMuted = info.IsMuted;
  313. session.PlayState.VolumeLevel = info.VolumeLevel;
  314. session.PlayState.AudioStreamIndex = info.AudioStreamIndex;
  315. session.PlayState.SubtitleStreamIndex = info.SubtitleStreamIndex;
  316. session.PlayState.PlayMethod = info.PlayMethod;
  317. }
  318. /// <summary>
  319. /// Removes the now playing item id.
  320. /// </summary>
  321. /// <param name="session">The session.</param>
  322. /// <exception cref="System.ArgumentNullException">item</exception>
  323. private void RemoveNowPlayingItem(SessionInfo session)
  324. {
  325. session.NowPlayingItem = null;
  326. session.PlayState = new PlayerStateInfo();
  327. if (!string.IsNullOrEmpty(session.DeviceId))
  328. {
  329. ClearTranscodingInfo(session.DeviceId);
  330. }
  331. }
  332. private string GetSessionKey(string clientType, string appVersion, string deviceId)
  333. {
  334. return clientType + deviceId;
  335. }
  336. /// <summary>
  337. /// Gets the connection.
  338. /// </summary>
  339. /// <param name="clientType">Type of the client.</param>
  340. /// <param name="appVersion">The app version.</param>
  341. /// <param name="deviceId">The device id.</param>
  342. /// <param name="deviceName">Name of the device.</param>
  343. /// <param name="remoteEndPoint">The remote end point.</param>
  344. /// <param name="userId">The user identifier.</param>
  345. /// <param name="username">The username.</param>
  346. /// <returns>SessionInfo.</returns>
  347. private async Task<SessionInfo> GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Guid? userId, string username)
  348. {
  349. var key = GetSessionKey(clientType, appVersion, deviceId);
  350. await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
  351. try
  352. {
  353. SessionInfo connection;
  354. DeviceInfo device = null;
  355. if (!_activeConnections.TryGetValue(key, out connection))
  356. {
  357. var sessionInfo = new SessionInfo
  358. {
  359. Client = clientType,
  360. DeviceId = deviceId,
  361. ApplicationVersion = appVersion,
  362. Id = Guid.NewGuid().ToString("N")
  363. };
  364. sessionInfo.DeviceName = deviceName;
  365. sessionInfo.UserId = userId;
  366. sessionInfo.UserName = username;
  367. sessionInfo.RemoteEndPoint = remoteEndPoint;
  368. OnSessionStarted(sessionInfo);
  369. _activeConnections.TryAdd(key, sessionInfo);
  370. connection = sessionInfo;
  371. if (!string.IsNullOrEmpty(deviceId))
  372. {
  373. var userIdString = userId.HasValue ? userId.Value.ToString("N") : null;
  374. device = await _deviceManager.RegisterDevice(deviceId, deviceName, clientType, userIdString).ConfigureAwait(false);
  375. }
  376. }
  377. device = device ?? _deviceManager.GetDevice(deviceId);
  378. if (!string.IsNullOrEmpty(device.CustomName))
  379. {
  380. deviceName = device.CustomName;
  381. }
  382. connection.DeviceName = deviceName;
  383. connection.UserId = userId;
  384. connection.UserName = username;
  385. connection.RemoteEndPoint = remoteEndPoint;
  386. if (!userId.HasValue)
  387. {
  388. connection.AdditionalUsers.Clear();
  389. }
  390. if (connection.SessionController == null)
  391. {
  392. connection.SessionController = _sessionFactories
  393. .Select(i => i.GetSessionController(connection))
  394. .FirstOrDefault(i => i != null);
  395. }
  396. return connection;
  397. }
  398. finally
  399. {
  400. _sessionLock.Release();
  401. }
  402. }
  403. private List<User> GetUsers(SessionInfo session)
  404. {
  405. var users = new List<User>();
  406. if (session.UserId.HasValue)
  407. {
  408. var user = _userManager.GetUserById(session.UserId.Value);
  409. if (user == null)
  410. {
  411. throw new InvalidOperationException("User not found");
  412. }
  413. users.Add(user);
  414. var additionalUsers = session.AdditionalUsers
  415. .Select(i => _userManager.GetUserById(i.UserId))
  416. .Where(i => i != null);
  417. users.AddRange(additionalUsers);
  418. }
  419. return users;
  420. }
  421. private Timer _idleTimer;
  422. private void StartIdleCheckTimer()
  423. {
  424. if (_idleTimer == null)
  425. {
  426. _idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
  427. }
  428. }
  429. private void StopIdleCheckTimer()
  430. {
  431. if (_idleTimer != null)
  432. {
  433. _idleTimer.Dispose();
  434. _idleTimer = null;
  435. }
  436. }
  437. private async void CheckForIdlePlayback(object state)
  438. {
  439. var playingSessions = Sessions.Where(i => i.NowPlayingItem != null)
  440. .ToList();
  441. if (playingSessions.Count > 0)
  442. {
  443. var idle = playingSessions
  444. .Where(i => (DateTime.UtcNow - i.LastPlaybackCheckIn).TotalMinutes > 5)
  445. .ToList();
  446. foreach (var session in idle)
  447. {
  448. _logger.Debug("Session {0} has gone idle while playing", session.Id);
  449. try
  450. {
  451. await OnPlaybackStopped(new PlaybackStopInfo
  452. {
  453. Item = session.NowPlayingItem,
  454. ItemId = (session.NowPlayingItem == null ? null : session.NowPlayingItem.Id),
  455. SessionId = session.Id,
  456. MediaSourceId = (session.PlayState == null ? null : session.PlayState.MediaSourceId)
  457. });
  458. }
  459. catch (Exception ex)
  460. {
  461. _logger.Debug("Error calling OnPlaybackStopped", ex);
  462. }
  463. }
  464. playingSessions = Sessions.Where(i => i.NowPlayingItem != null)
  465. .ToList();
  466. }
  467. if (playingSessions.Count == 0)
  468. {
  469. StopIdleCheckTimer();
  470. }
  471. }
  472. /// <summary>
  473. /// Used to report that playback has started for an item
  474. /// </summary>
  475. /// <param name="info">The info.</param>
  476. /// <returns>Task.</returns>
  477. /// <exception cref="System.ArgumentNullException">info</exception>
  478. public async Task OnPlaybackStart(PlaybackStartInfo info)
  479. {
  480. if (info == null)
  481. {
  482. throw new ArgumentNullException("info");
  483. }
  484. var session = GetSession(info.SessionId);
  485. var libraryItem = string.IsNullOrWhiteSpace(info.ItemId)
  486. ? null
  487. : _libraryManager.GetItemById(new Guid(info.ItemId));
  488. UpdateNowPlayingItem(session, info, libraryItem);
  489. if (!string.IsNullOrEmpty(session.DeviceId) && info.PlayMethod != PlayMethod.Transcode)
  490. {
  491. ClearTranscodingInfo(session.DeviceId);
  492. }
  493. session.QueueableMediaTypes = info.QueueableMediaTypes;
  494. var users = GetUsers(session);
  495. if (libraryItem != null)
  496. {
  497. var key = libraryItem.GetUserDataKey();
  498. foreach (var user in users)
  499. {
  500. await OnPlaybackStart(user.Id, key, libraryItem).ConfigureAwait(false);
  501. }
  502. }
  503. // Nothing to save here
  504. // Fire events to inform plugins
  505. EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
  506. {
  507. Item = libraryItem,
  508. Users = users,
  509. MediaSourceId = info.MediaSourceId,
  510. MediaInfo = info.Item,
  511. DeviceName = session.DeviceName,
  512. ClientName = session.Client,
  513. DeviceId = session.DeviceId
  514. }, _logger);
  515. await SendPlaybackStartNotification(session, CancellationToken.None).ConfigureAwait(false);
  516. StartIdleCheckTimer();
  517. }
  518. /// <summary>
  519. /// Called when [playback start].
  520. /// </summary>
  521. /// <param name="userId">The user identifier.</param>
  522. /// <param name="userDataKey">The user data key.</param>
  523. /// <param name="item">The item.</param>
  524. /// <returns>Task.</returns>
  525. private async Task OnPlaybackStart(Guid userId, string userDataKey, IHasUserData item)
  526. {
  527. var data = _userDataRepository.GetUserData(userId, userDataKey);
  528. data.PlayCount++;
  529. data.LastPlayedDate = DateTime.UtcNow;
  530. if (!(item is Video))
  531. {
  532. data.Played = true;
  533. }
  534. await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false);
  535. }
  536. /// <summary>
  537. /// Used to report playback progress for an item
  538. /// </summary>
  539. /// <param name="info">The info.</param>
  540. /// <returns>Task.</returns>
  541. /// <exception cref="System.ArgumentNullException"></exception>
  542. /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception>
  543. public async Task OnPlaybackProgress(PlaybackProgressInfo info)
  544. {
  545. if (info == null)
  546. {
  547. throw new ArgumentNullException("info");
  548. }
  549. var session = GetSession(info.SessionId);
  550. var libraryItem = string.IsNullOrWhiteSpace(info.ItemId)
  551. ? null
  552. : _libraryManager.GetItemById(new Guid(info.ItemId));
  553. UpdateNowPlayingItem(session, info, libraryItem);
  554. var users = GetUsers(session);
  555. if (libraryItem != null)
  556. {
  557. var key = libraryItem.GetUserDataKey();
  558. foreach (var user in users)
  559. {
  560. await OnPlaybackProgress(user.Id, key, libraryItem, info.PositionTicks).ConfigureAwait(false);
  561. }
  562. }
  563. EventHelper.FireEventIfNotNull(PlaybackProgress, this, new PlaybackProgressEventArgs
  564. {
  565. Item = libraryItem,
  566. Users = users,
  567. PlaybackPositionTicks = session.PlayState.PositionTicks,
  568. MediaSourceId = session.PlayState.MediaSourceId,
  569. MediaInfo = info.Item,
  570. DeviceName = session.DeviceName,
  571. ClientName = session.Client,
  572. DeviceId = session.DeviceId
  573. }, _logger);
  574. StartIdleCheckTimer();
  575. }
  576. private async Task OnPlaybackProgress(Guid userId, string userDataKey, BaseItem item, long? positionTicks)
  577. {
  578. var data = _userDataRepository.GetUserData(userId, userDataKey);
  579. if (positionTicks.HasValue)
  580. {
  581. UpdatePlayState(item, data, positionTicks.Value);
  582. await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
  583. }
  584. }
  585. /// <summary>
  586. /// Used to report that playback has ended for an item
  587. /// </summary>
  588. /// <param name="info">The info.</param>
  589. /// <returns>Task.</returns>
  590. /// <exception cref="System.ArgumentNullException">info</exception>
  591. /// <exception cref="System.ArgumentOutOfRangeException">positionTicks</exception>
  592. public async Task OnPlaybackStopped(PlaybackStopInfo info)
  593. {
  594. if (info == null)
  595. {
  596. throw new ArgumentNullException("info");
  597. }
  598. if (info.PositionTicks.HasValue && info.PositionTicks.Value < 0)
  599. {
  600. throw new ArgumentOutOfRangeException("positionTicks");
  601. }
  602. var session = GetSession(info.SessionId);
  603. var libraryItem = string.IsNullOrWhiteSpace(info.ItemId)
  604. ? null
  605. : _libraryManager.GetItemById(new Guid(info.ItemId));
  606. // Normalize
  607. if (string.IsNullOrWhiteSpace(info.MediaSourceId))
  608. {
  609. info.MediaSourceId = info.ItemId;
  610. }
  611. if (!string.IsNullOrWhiteSpace(info.ItemId) && libraryItem != null)
  612. {
  613. var current = session.NowPlayingItem;
  614. if (current == null || !string.Equals(current.Id, info.ItemId, StringComparison.OrdinalIgnoreCase))
  615. {
  616. info.Item = GetItemInfo(libraryItem, libraryItem, info.MediaSourceId);
  617. }
  618. else
  619. {
  620. info.Item = current;
  621. }
  622. }
  623. RemoveNowPlayingItem(session);
  624. var users = GetUsers(session);
  625. var playedToCompletion = false;
  626. if (libraryItem != null)
  627. {
  628. var key = libraryItem.GetUserDataKey();
  629. foreach (var user in users)
  630. {
  631. playedToCompletion = await OnPlaybackStopped(user.Id, key, libraryItem, info.PositionTicks).ConfigureAwait(false);
  632. }
  633. }
  634. EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs
  635. {
  636. Item = libraryItem,
  637. Users = users,
  638. PlaybackPositionTicks = info.PositionTicks,
  639. PlayedToCompletion = playedToCompletion,
  640. MediaSourceId = info.MediaSourceId,
  641. MediaInfo = info.Item,
  642. DeviceName = session.DeviceName,
  643. ClientName = session.Client,
  644. DeviceId = session.DeviceId
  645. }, _logger);
  646. await SendPlaybackStoppedNotification(session, CancellationToken.None).ConfigureAwait(false);
  647. }
  648. private async Task<bool> OnPlaybackStopped(Guid userId, string userDataKey, BaseItem item, long? positionTicks)
  649. {
  650. var data = _userDataRepository.GetUserData(userId, userDataKey);
  651. bool playedToCompletion;
  652. if (positionTicks.HasValue)
  653. {
  654. playedToCompletion = UpdatePlayState(item, data, positionTicks.Value);
  655. }
  656. else
  657. {
  658. // If the client isn't able to report this, then we'll just have to make an assumption
  659. data.PlayCount++;
  660. data.Played = true;
  661. data.PlaybackPositionTicks = 0;
  662. playedToCompletion = true;
  663. }
  664. await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false);
  665. return playedToCompletion;
  666. }
  667. /// <summary>
  668. /// Updates playstate position for an item but does not save
  669. /// </summary>
  670. /// <param name="item">The item</param>
  671. /// <param name="data">User data for the item</param>
  672. /// <param name="positionTicks">The current playback position</param>
  673. private bool UpdatePlayState(BaseItem item, UserItemData data, long positionTicks)
  674. {
  675. var playedToCompletion = false;
  676. var hasRuntime = item.RunTimeTicks.HasValue && item.RunTimeTicks > 0;
  677. // If a position has been reported, and if we know the duration
  678. if (positionTicks > 0 && hasRuntime)
  679. {
  680. var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100;
  681. // Don't track in very beginning
  682. if (pctIn < _configurationManager.Configuration.MinResumePct)
  683. {
  684. positionTicks = 0;
  685. }
  686. // If we're at the end, assume completed
  687. else if (pctIn > _configurationManager.Configuration.MaxResumePct || positionTicks >= item.RunTimeTicks.Value)
  688. {
  689. positionTicks = 0;
  690. data.Played = playedToCompletion = true;
  691. }
  692. else
  693. {
  694. // Enforce MinResumeDuration
  695. var durationSeconds = TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds;
  696. if (durationSeconds < _configurationManager.Configuration.MinResumeDurationSeconds)
  697. {
  698. positionTicks = 0;
  699. data.Played = playedToCompletion = true;
  700. }
  701. }
  702. }
  703. else if (!hasRuntime)
  704. {
  705. // If we don't know the runtime we'll just have to assume it was fully played
  706. data.Played = playedToCompletion = true;
  707. positionTicks = 0;
  708. }
  709. if (item is Audio)
  710. {
  711. positionTicks = 0;
  712. }
  713. data.PlaybackPositionTicks = positionTicks;
  714. return playedToCompletion;
  715. }
  716. /// <summary>
  717. /// Gets the session.
  718. /// </summary>
  719. /// <param name="sessionId">The session identifier.</param>
  720. /// <param name="throwOnMissing">if set to <c>true</c> [throw on missing].</param>
  721. /// <returns>SessionInfo.</returns>
  722. /// <exception cref="ResourceNotFoundException"></exception>
  723. private SessionInfo GetSession(string sessionId, bool throwOnMissing = true)
  724. {
  725. var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId));
  726. if (session == null && throwOnMissing)
  727. {
  728. throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
  729. }
  730. return session;
  731. }
  732. public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken)
  733. {
  734. var generalCommand = new GeneralCommand
  735. {
  736. Name = GeneralCommandType.DisplayMessage.ToString()
  737. };
  738. generalCommand.Arguments["Header"] = command.Header;
  739. generalCommand.Arguments["Text"] = command.Text;
  740. if (command.TimeoutMs.HasValue)
  741. {
  742. generalCommand.Arguments["TimeoutMs"] = command.TimeoutMs.Value.ToString(CultureInfo.InvariantCulture);
  743. }
  744. return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken);
  745. }
  746. public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken)
  747. {
  748. var session = GetSession(sessionId);
  749. var controllingSession = GetSession(controllingSessionId);
  750. AssertCanControl(session, controllingSession);
  751. return session.SessionController.SendGeneralCommand(command, cancellationToken);
  752. }
  753. public Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
  754. {
  755. var session = GetSession(sessionId);
  756. var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null;
  757. List<BaseItem> items;
  758. if (command.PlayCommand == PlayCommand.PlayInstantMix)
  759. {
  760. items = command.ItemIds.SelectMany(i => TranslateItemForInstantMix(i, user))
  761. .Where(i => i.LocationType != LocationType.Virtual)
  762. .ToList();
  763. command.PlayCommand = PlayCommand.PlayNow;
  764. }
  765. else
  766. {
  767. items = command.ItemIds.SelectMany(i => TranslateItemForPlayback(i, user))
  768. .Where(i => i.LocationType != LocationType.Virtual)
  769. .ToList();
  770. }
  771. if (command.PlayCommand == PlayCommand.PlayShuffle)
  772. {
  773. items = items.OrderBy(i => Guid.NewGuid()).ToList();
  774. command.PlayCommand = PlayCommand.PlayNow;
  775. }
  776. command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray();
  777. if (user != null)
  778. {
  779. if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full))
  780. {
  781. throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name));
  782. }
  783. }
  784. if (command.PlayCommand != PlayCommand.PlayNow)
  785. {
  786. if (items.Any(i => !session.QueueableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
  787. {
  788. throw new ArgumentException(string.Format("{0} is unable to queue the requested media type.", session.DeviceName ?? session.Id.ToString()));
  789. }
  790. }
  791. else
  792. {
  793. if (items.Any(i => !session.PlayableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
  794. {
  795. throw new ArgumentException(string.Format("{0} is unable to play the requested media type.", session.DeviceName ?? session.Id.ToString()));
  796. }
  797. }
  798. var controllingSession = GetSession(controllingSessionId);
  799. AssertCanControl(session, controllingSession);
  800. if (controllingSession.UserId.HasValue)
  801. {
  802. command.ControllingUserId = controllingSession.UserId.Value.ToString("N");
  803. }
  804. return session.SessionController.SendPlayCommand(command, cancellationToken);
  805. }
  806. private IEnumerable<BaseItem> TranslateItemForPlayback(string id, User user)
  807. {
  808. var item = _libraryManager.GetItemById(new Guid(id));
  809. if (item.IsFolder)
  810. {
  811. var folder = (Folder)item;
  812. var items = user == null ? folder.RecursiveChildren :
  813. folder.GetRecursiveChildren(user);
  814. items = items.Where(i => !i.IsFolder);
  815. items = items.OrderBy(i => i.SortName);
  816. return items;
  817. }
  818. return new[] { item };
  819. }
  820. private IEnumerable<BaseItem> TranslateItemForInstantMix(string id, User user)
  821. {
  822. var item = _libraryManager.GetItemById(new Guid(id));
  823. var audio = item as Audio;
  824. if (audio != null)
  825. {
  826. return _musicManager.GetInstantMixFromSong(audio, user);
  827. }
  828. var artist = item as MusicArtist;
  829. if (artist != null)
  830. {
  831. return _musicManager.GetInstantMixFromArtist(artist.Name, user);
  832. }
  833. var album = item as MusicAlbum;
  834. if (album != null)
  835. {
  836. return _musicManager.GetInstantMixFromAlbum(album, user);
  837. }
  838. var genre = item as MusicGenre;
  839. if (genre != null)
  840. {
  841. return _musicManager.GetInstantMixFromGenres(new[] { genre.Name }, user);
  842. }
  843. return new BaseItem[] { };
  844. }
  845. public Task SendBrowseCommand(string controllingSessionId, string sessionId, BrowseRequest command, CancellationToken cancellationToken)
  846. {
  847. var generalCommand = new GeneralCommand
  848. {
  849. Name = GeneralCommandType.DisplayContent.ToString()
  850. };
  851. generalCommand.Arguments["ItemId"] = command.ItemId;
  852. generalCommand.Arguments["ItemName"] = command.ItemName;
  853. generalCommand.Arguments["ItemType"] = command.ItemType;
  854. return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken);
  855. }
  856. public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken)
  857. {
  858. var session = GetSession(sessionId);
  859. var controllingSession = GetSession(controllingSessionId);
  860. AssertCanControl(session, controllingSession);
  861. if (controllingSession.UserId.HasValue)
  862. {
  863. command.ControllingUserId = controllingSession.UserId.Value.ToString("N");
  864. }
  865. return session.SessionController.SendPlaystateCommand(command, cancellationToken);
  866. }
  867. private void AssertCanControl(SessionInfo session, SessionInfo controllingSession)
  868. {
  869. if (session == null)
  870. {
  871. throw new ArgumentNullException("session");
  872. }
  873. if (controllingSession == null)
  874. {
  875. throw new ArgumentNullException("controllingSession");
  876. }
  877. }
  878. /// <summary>
  879. /// Sends the restart required message.
  880. /// </summary>
  881. /// <param name="cancellationToken">The cancellation token.</param>
  882. /// <returns>Task.</returns>
  883. public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
  884. {
  885. var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
  886. var info = _appHost.GetSystemInfo();
  887. var tasks = sessions.Select(session => Task.Run(async () =>
  888. {
  889. try
  890. {
  891. await session.SessionController.SendRestartRequiredNotification(info, cancellationToken).ConfigureAwait(false);
  892. }
  893. catch (Exception ex)
  894. {
  895. _logger.ErrorException("Error in SendRestartRequiredNotification.", ex);
  896. }
  897. }, cancellationToken));
  898. return Task.WhenAll(tasks);
  899. }
  900. /// <summary>
  901. /// Sends the server shutdown notification.
  902. /// </summary>
  903. /// <param name="cancellationToken">The cancellation token.</param>
  904. /// <returns>Task.</returns>
  905. public Task SendServerShutdownNotification(CancellationToken cancellationToken)
  906. {
  907. var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
  908. var tasks = sessions.Select(session => Task.Run(async () =>
  909. {
  910. try
  911. {
  912. await session.SessionController.SendServerShutdownNotification(cancellationToken).ConfigureAwait(false);
  913. }
  914. catch (Exception ex)
  915. {
  916. _logger.ErrorException("Error in SendServerShutdownNotification.", ex);
  917. }
  918. }, cancellationToken));
  919. return Task.WhenAll(tasks);
  920. }
  921. /// <summary>
  922. /// Sends the server restart notification.
  923. /// </summary>
  924. /// <param name="cancellationToken">The cancellation token.</param>
  925. /// <returns>Task.</returns>
  926. public Task SendServerRestartNotification(CancellationToken cancellationToken)
  927. {
  928. _logger.Debug("Beginning SendServerRestartNotification");
  929. var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
  930. var tasks = sessions.Select(session => Task.Run(async () =>
  931. {
  932. try
  933. {
  934. await session.SessionController.SendServerRestartNotification(cancellationToken).ConfigureAwait(false);
  935. }
  936. catch (Exception ex)
  937. {
  938. _logger.ErrorException("Error in SendServerRestartNotification.", ex);
  939. }
  940. }, cancellationToken));
  941. return Task.WhenAll(tasks);
  942. }
  943. public Task SendSessionEndedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken)
  944. {
  945. var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
  946. var dto = GetSessionInfoDto(sessionInfo);
  947. var tasks = sessions.Select(session => Task.Run(async () =>
  948. {
  949. try
  950. {
  951. await session.SessionController.SendSessionEndedNotification(dto, cancellationToken).ConfigureAwait(false);
  952. }
  953. catch (Exception ex)
  954. {
  955. _logger.ErrorException("Error in SendSessionEndedNotification.", ex);
  956. }
  957. }, cancellationToken));
  958. return Task.WhenAll(tasks);
  959. }
  960. public Task SendPlaybackStartNotification(SessionInfo sessionInfo, CancellationToken cancellationToken)
  961. {
  962. var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
  963. var dto = GetSessionInfoDto(sessionInfo);
  964. var tasks = sessions.Select(session => Task.Run(async () =>
  965. {
  966. try
  967. {
  968. await session.SessionController.SendPlaybackStartNotification(dto, cancellationToken).ConfigureAwait(false);
  969. }
  970. catch (Exception ex)
  971. {
  972. _logger.ErrorException("Error in SendPlaybackStartNotification.", ex);
  973. }
  974. }, cancellationToken));
  975. return Task.WhenAll(tasks);
  976. }
  977. public Task SendPlaybackStoppedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken)
  978. {
  979. var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
  980. var dto = GetSessionInfoDto(sessionInfo);
  981. var tasks = sessions.Select(session => Task.Run(async () =>
  982. {
  983. try
  984. {
  985. await session.SessionController.SendPlaybackStoppedNotification(dto, cancellationToken).ConfigureAwait(false);
  986. }
  987. catch (Exception ex)
  988. {
  989. _logger.ErrorException("Error in SendPlaybackStoppedNotification.", ex);
  990. }
  991. }, cancellationToken));
  992. return Task.WhenAll(tasks);
  993. }
  994. /// <summary>
  995. /// Adds the additional user.
  996. /// </summary>
  997. /// <param name="sessionId">The session identifier.</param>
  998. /// <param name="userId">The user identifier.</param>
  999. /// <exception cref="System.UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception>
  1000. /// <exception cref="System.ArgumentException">The requested user is already the primary user of the session.</exception>
  1001. public void AddAdditionalUser(string sessionId, Guid userId)
  1002. {
  1003. var session = GetSession(sessionId);
  1004. if (session.UserId.HasValue && session.UserId.Value == userId)
  1005. {
  1006. throw new ArgumentException("The requested user is already the primary user of the session.");
  1007. }
  1008. if (session.AdditionalUsers.All(i => new Guid(i.UserId) != userId))
  1009. {
  1010. var user = _userManager.GetUserById(userId);
  1011. session.AdditionalUsers.Add(new SessionUserInfo
  1012. {
  1013. UserId = userId.ToString("N"),
  1014. UserName = user.Name
  1015. });
  1016. }
  1017. }
  1018. /// <summary>
  1019. /// Removes the additional user.
  1020. /// </summary>
  1021. /// <param name="sessionId">The session identifier.</param>
  1022. /// <param name="userId">The user identifier.</param>
  1023. /// <exception cref="System.UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception>
  1024. /// <exception cref="System.ArgumentException">The requested user is already the primary user of the session.</exception>
  1025. public void RemoveAdditionalUser(string sessionId, Guid userId)
  1026. {
  1027. var session = GetSession(sessionId);
  1028. if (session.UserId.HasValue && session.UserId.Value == userId)
  1029. {
  1030. throw new ArgumentException("The requested user is already the primary user of the session.");
  1031. }
  1032. var user = session.AdditionalUsers.FirstOrDefault(i => new Guid(i.UserId) == userId);
  1033. if (user != null)
  1034. {
  1035. session.AdditionalUsers.Remove(user);
  1036. }
  1037. }
  1038. public void ValidateSecurityToken(string token)
  1039. {
  1040. if (string.IsNullOrWhiteSpace(token))
  1041. {
  1042. throw new AuthenticationException();
  1043. }
  1044. var result = _authRepo.Get(new AuthenticationInfoQuery
  1045. {
  1046. AccessToken = token
  1047. });
  1048. var info = result.Items.FirstOrDefault();
  1049. if (info == null)
  1050. {
  1051. throw new AuthenticationException();
  1052. }
  1053. if (!info.IsActive)
  1054. {
  1055. throw new AuthenticationException("Access token has expired.");
  1056. }
  1057. if (!string.IsNullOrWhiteSpace(info.UserId))
  1058. {
  1059. var user = _userManager.GetUserById(info.UserId);
  1060. if (user == null || user.Configuration.IsDisabled)
  1061. {
  1062. throw new AuthenticationException("User account has been disabled.");
  1063. }
  1064. }
  1065. }
  1066. /// <summary>
  1067. /// Authenticates the new session.
  1068. /// </summary>
  1069. /// <param name="request">The request.</param>
  1070. /// <returns>Task{SessionInfo}.</returns>
  1071. /// <exception cref="AuthenticationException">Invalid user or password entered.</exception>
  1072. /// <exception cref="System.UnauthorizedAccessException">Invalid user or password entered.</exception>
  1073. /// <exception cref="UnauthorizedAccessException">Invalid user or password entered.</exception>
  1074. public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
  1075. {
  1076. var user = _userManager.Users
  1077. .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
  1078. var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
  1079. if (!result)
  1080. {
  1081. EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
  1082. throw new AuthenticationException("Invalid user or password entered.");
  1083. }
  1084. var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.DeviceName).ConfigureAwait(false);
  1085. EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
  1086. var session = await LogSessionActivity(request.App,
  1087. request.AppVersion,
  1088. request.DeviceId,
  1089. request.DeviceName,
  1090. request.RemoteEndPoint,
  1091. user)
  1092. .ConfigureAwait(false);
  1093. return new AuthenticationResult
  1094. {
  1095. User = _userManager.GetUserDto(user, request.RemoteEndPoint),
  1096. SessionInfo = GetSessionInfoDto(session),
  1097. AccessToken = token,
  1098. ServerId = _appHost.SystemId
  1099. };
  1100. }
  1101. private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string deviceName)
  1102. {
  1103. var existing = _authRepo.Get(new AuthenticationInfoQuery
  1104. {
  1105. DeviceId = deviceId,
  1106. IsActive = true,
  1107. UserId = userId,
  1108. Limit = 1
  1109. });
  1110. if (existing.Items.Length > 0)
  1111. {
  1112. _logger.Debug("Reissuing access token");
  1113. return existing.Items[0].AccessToken;
  1114. }
  1115. var newToken = new AuthenticationInfo
  1116. {
  1117. AppName = app,
  1118. DateCreated = DateTime.UtcNow,
  1119. DeviceId = deviceId,
  1120. DeviceName = deviceName,
  1121. UserId = userId,
  1122. IsActive = true,
  1123. AccessToken = Guid.NewGuid().ToString("N")
  1124. };
  1125. _logger.Debug("Creating new access token for user {0}", userId);
  1126. await _authRepo.Create(newToken, CancellationToken.None).ConfigureAwait(false);
  1127. return newToken.AccessToken;
  1128. }
  1129. public async Task Logout(string accessToken)
  1130. {
  1131. if (string.IsNullOrWhiteSpace(accessToken))
  1132. {
  1133. throw new ArgumentNullException("accessToken");
  1134. }
  1135. var existing = _authRepo.Get(new AuthenticationInfoQuery
  1136. {
  1137. Limit = 1,
  1138. AccessToken = accessToken
  1139. }).Items.FirstOrDefault();
  1140. if (existing != null)
  1141. {
  1142. existing.IsActive = false;
  1143. await _authRepo.Update(existing, CancellationToken.None).ConfigureAwait(false);
  1144. var sessions = Sessions
  1145. .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase))
  1146. .ToList();
  1147. foreach (var session in sessions)
  1148. {
  1149. try
  1150. {
  1151. ReportSessionEnded(session.Id);
  1152. }
  1153. catch (Exception ex)
  1154. {
  1155. _logger.ErrorException("Error reporting session ended", ex);
  1156. }
  1157. }
  1158. }
  1159. }
  1160. public async Task RevokeUserTokens(string userId)
  1161. {
  1162. var existing = _authRepo.Get(new AuthenticationInfoQuery
  1163. {
  1164. IsActive = true,
  1165. UserId = userId
  1166. });
  1167. foreach (var info in existing.Items)
  1168. {
  1169. await Logout(info.AccessToken).ConfigureAwait(false);
  1170. }
  1171. }
  1172. public Task RevokeToken(string token)
  1173. {
  1174. return Logout(token);
  1175. }
  1176. /// <summary>
  1177. /// Reports the capabilities.
  1178. /// </summary>
  1179. /// <param name="sessionId">The session identifier.</param>
  1180. /// <param name="capabilities">The capabilities.</param>
  1181. public void ReportCapabilities(string sessionId, ClientCapabilities capabilities)
  1182. {
  1183. var session = GetSession(sessionId);
  1184. ReportCapabilities(session, capabilities, true);
  1185. }
  1186. private async void ReportCapabilities(SessionInfo session,
  1187. ClientCapabilities capabilities,
  1188. bool saveCapabilities)
  1189. {
  1190. session.PlayableMediaTypes = capabilities.PlayableMediaTypes;
  1191. session.SupportedCommands = capabilities.SupportedCommands;
  1192. if (!string.IsNullOrWhiteSpace(capabilities.MessageCallbackUrl))
  1193. {
  1194. var controller = session.SessionController as HttpSessionController;
  1195. if (controller == null)
  1196. {
  1197. session.SessionController = new HttpSessionController(_httpClient, _jsonSerializer, session, capabilities.MessageCallbackUrl, this);
  1198. }
  1199. }
  1200. EventHelper.FireEventIfNotNull(CapabilitiesChanged, this, new SessionEventArgs
  1201. {
  1202. SessionInfo = session
  1203. }, _logger);
  1204. if (saveCapabilities)
  1205. {
  1206. await SaveCapabilities(session.DeviceId, capabilities).ConfigureAwait(false);
  1207. }
  1208. }
  1209. private ClientCapabilities GetSavedCapabilities(string deviceId)
  1210. {
  1211. return _deviceManager.GetCapabilities(deviceId);
  1212. }
  1213. private Task SaveCapabilities(string deviceId, ClientCapabilities capabilities)
  1214. {
  1215. return _deviceManager.SaveCapabilities(deviceId, capabilities);
  1216. }
  1217. public SessionInfoDto GetSessionInfoDto(SessionInfo session)
  1218. {
  1219. var dto = new SessionInfoDto
  1220. {
  1221. Client = session.Client,
  1222. DeviceId = session.DeviceId,
  1223. DeviceName = session.DeviceName,
  1224. Id = session.Id,
  1225. LastActivityDate = session.LastActivityDate,
  1226. NowViewingItem = session.NowViewingItem,
  1227. ApplicationVersion = session.ApplicationVersion,
  1228. QueueableMediaTypes = session.QueueableMediaTypes,
  1229. PlayableMediaTypes = session.PlayableMediaTypes,
  1230. AdditionalUsers = session.AdditionalUsers,
  1231. SupportedCommands = session.SupportedCommands,
  1232. UserName = session.UserName,
  1233. NowPlayingItem = session.NowPlayingItem,
  1234. SupportsRemoteControl = session.SupportsMediaControl,
  1235. PlayState = session.PlayState,
  1236. TranscodingInfo = session.NowPlayingItem == null ? null : session.TranscodingInfo
  1237. };
  1238. if (session.UserId.HasValue)
  1239. {
  1240. dto.UserId = session.UserId.Value.ToString("N");
  1241. var user = _userManager.GetUserById(session.UserId.Value);
  1242. if (user != null)
  1243. {
  1244. dto.UserPrimaryImageTag = GetImageCacheTag(user, ImageType.Primary);
  1245. }
  1246. }
  1247. return dto;
  1248. }
  1249. /// <summary>
  1250. /// Converts a BaseItem to a BaseItemInfo
  1251. /// </summary>
  1252. /// <param name="item">The item.</param>
  1253. /// <param name="chapterOwner">The chapter owner.</param>
  1254. /// <param name="mediaSourceId">The media source identifier.</param>
  1255. /// <returns>BaseItemInfo.</returns>
  1256. /// <exception cref="System.ArgumentNullException">item</exception>
  1257. private BaseItemInfo GetItemInfo(BaseItem item, BaseItem chapterOwner, string mediaSourceId)
  1258. {
  1259. if (item == null)
  1260. {
  1261. throw new ArgumentNullException("item");
  1262. }
  1263. var info = new BaseItemInfo
  1264. {
  1265. Id = GetDtoId(item),
  1266. Name = item.Name,
  1267. MediaType = item.MediaType,
  1268. Type = item.GetClientTypeName(),
  1269. RunTimeTicks = item.RunTimeTicks,
  1270. IndexNumber = item.IndexNumber,
  1271. ParentIndexNumber = item.ParentIndexNumber,
  1272. PremiereDate = item.PremiereDate,
  1273. ProductionYear = item.ProductionYear
  1274. };
  1275. info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary);
  1276. if (info.PrimaryImageTag != null)
  1277. {
  1278. info.PrimaryImageItemId = GetDtoId(item);
  1279. }
  1280. var episode = item as Episode;
  1281. if (episode != null)
  1282. {
  1283. info.IndexNumberEnd = episode.IndexNumberEnd;
  1284. }
  1285. var hasSeries = item as IHasSeries;
  1286. if (hasSeries != null)
  1287. {
  1288. info.SeriesName = hasSeries.SeriesName;
  1289. }
  1290. var recording = item as ILiveTvRecording;
  1291. if (recording != null && recording.RecordingInfo != null)
  1292. {
  1293. if (recording.RecordingInfo.IsSeries)
  1294. {
  1295. info.Name = recording.RecordingInfo.EpisodeTitle;
  1296. info.SeriesName = recording.RecordingInfo.Name;
  1297. if (string.IsNullOrWhiteSpace(info.Name))
  1298. {
  1299. info.Name = recording.RecordingInfo.Name;
  1300. }
  1301. }
  1302. }
  1303. var audio = item as Audio;
  1304. if (audio != null)
  1305. {
  1306. info.Album = audio.Album;
  1307. info.Artists = audio.Artists;
  1308. if (info.PrimaryImageTag == null)
  1309. {
  1310. var album = audio.Parents.OfType<MusicAlbum>().FirstOrDefault();
  1311. if (album != null && album.HasImage(ImageType.Primary))
  1312. {
  1313. info.PrimaryImageTag = GetImageCacheTag(album, ImageType.Primary);
  1314. if (info.PrimaryImageTag != null)
  1315. {
  1316. info.PrimaryImageItemId = GetDtoId(album);
  1317. }
  1318. }
  1319. }
  1320. }
  1321. var musicVideo = item as MusicVideo;
  1322. if (musicVideo != null)
  1323. {
  1324. info.Album = musicVideo.Album;
  1325. info.Artists = musicVideo.Artists.ToList();
  1326. }
  1327. var backropItem = item.HasImage(ImageType.Backdrop) ? item : null;
  1328. var thumbItem = item.HasImage(ImageType.Thumb) ? item : null;
  1329. var logoItem = item.HasImage(ImageType.Logo) ? item : null;
  1330. if (thumbItem == null)
  1331. {
  1332. if (episode != null)
  1333. {
  1334. var series = episode.Series;
  1335. if (series != null && series.HasImage(ImageType.Thumb))
  1336. {
  1337. thumbItem = series;
  1338. }
  1339. }
  1340. }
  1341. if (backropItem == null)
  1342. {
  1343. if (episode != null)
  1344. {
  1345. var series = episode.Series;
  1346. if (series != null && series.HasImage(ImageType.Backdrop))
  1347. {
  1348. backropItem = series;
  1349. }
  1350. }
  1351. }
  1352. if (backropItem == null)
  1353. {
  1354. backropItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Backdrop));
  1355. }
  1356. if (thumbItem == null)
  1357. {
  1358. thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb));
  1359. }
  1360. if (logoItem == null)
  1361. {
  1362. logoItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Logo));
  1363. }
  1364. if (thumbItem != null)
  1365. {
  1366. info.ThumbImageTag = GetImageCacheTag(thumbItem, ImageType.Thumb);
  1367. info.ThumbItemId = GetDtoId(thumbItem);
  1368. }
  1369. if (backropItem != null)
  1370. {
  1371. info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop);
  1372. info.BackdropItemId = GetDtoId(backropItem);
  1373. }
  1374. if (logoItem != null)
  1375. {
  1376. info.LogoImageTag = GetImageCacheTag(logoItem, ImageType.Logo);
  1377. info.LogoItemId = GetDtoId(logoItem);
  1378. }
  1379. if (chapterOwner != null)
  1380. {
  1381. info.ChapterImagesItemId = chapterOwner.Id.ToString("N");
  1382. info.Chapters = _dtoService.GetChapterInfoDtos(chapterOwner).ToList();
  1383. }
  1384. if (!string.IsNullOrWhiteSpace(mediaSourceId))
  1385. {
  1386. info.MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
  1387. {
  1388. ItemId = new Guid(mediaSourceId)
  1389. }).ToList();
  1390. }
  1391. return info;
  1392. }
  1393. private string GetImageCacheTag(BaseItem item, ImageType type)
  1394. {
  1395. try
  1396. {
  1397. return _imageProcessor.GetImageCacheTag(item, type);
  1398. }
  1399. catch (Exception ex)
  1400. {
  1401. _logger.ErrorException("Error getting {0} image info", ex, type);
  1402. return null;
  1403. }
  1404. }
  1405. private string GetDtoId(BaseItem item)
  1406. {
  1407. return _dtoService.GetDtoId(item);
  1408. }
  1409. public void ReportNowViewingItem(string sessionId, string itemId)
  1410. {
  1411. var item = _libraryManager.GetItemById(new Guid(itemId));
  1412. var info = GetItemInfo(item, null, null);
  1413. ReportNowViewingItem(sessionId, info);
  1414. }
  1415. public void ReportNowViewingItem(string sessionId, BaseItemInfo item)
  1416. {
  1417. var session = GetSession(sessionId);
  1418. session.NowViewingItem = item;
  1419. }
  1420. public void ReportTranscodingInfo(string deviceId, TranscodingInfo info)
  1421. {
  1422. var session = Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId));
  1423. if (session != null)
  1424. {
  1425. session.TranscodingInfo = info;
  1426. }
  1427. }
  1428. public void ClearTranscodingInfo(string deviceId)
  1429. {
  1430. ReportTranscodingInfo(deviceId, null);
  1431. }
  1432. public SessionInfo GetSession(string deviceId, string client, string version)
  1433. {
  1434. return Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) &&
  1435. string.Equals(i.Client, client));
  1436. }
  1437. }
  1438. }