Selaa lähdekoodia

Merge pull request #3324 from Ullmie02/api-sessionservice

Move SessionService to Jellyfin.Api
David 5 vuotta sitten
vanhempi
sitoutus
576ffeb2a9

+ 474 - 0
Jellyfin.Api/Controllers/SessionController.cs

@@ -0,0 +1,474 @@
+#pragma warning disable CA1801
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Session;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Jellyfin.Api.Controllers
+{
+    /// <summary>
+    /// The session controller.
+    /// </summary>
+    public class SessionController : BaseJellyfinApiController
+    {
+        private readonly ISessionManager _sessionManager;
+        private readonly IUserManager _userManager;
+        private readonly IAuthorizationContext _authContext;
+        private readonly IDeviceManager _deviceManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SessionController"/> class.
+        /// </summary>
+        /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
+        /// <param name="userManager">Instance of <see cref="IUserManager"/> interface.</param>
+        /// <param name="authContext">Instance of <see cref="IAuthorizationContext"/> interface.</param>
+        /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param>
+        public SessionController(
+            ISessionManager sessionManager,
+            IUserManager userManager,
+            IAuthorizationContext authContext,
+            IDeviceManager deviceManager)
+        {
+            _sessionManager = sessionManager;
+            _userManager = userManager;
+            _authContext = authContext;
+            _deviceManager = deviceManager;
+        }
+
+        /// <summary>
+        /// Gets a list of sessions.
+        /// </summary>
+        /// <param name="controllableByUserId">Filter by sessions that a given user is allowed to remote control.</param>
+        /// <param name="deviceId">Filter by device Id.</param>
+        /// <param name="activeWithinSeconds">Optional. Filter by sessions that were active in the last n seconds.</param>
+        /// <response code="200">List of sessions returned.</response>
+        /// <returns>An <see cref="IEnumerable{SessionInfo}"/> with the available sessions.</returns>
+        [HttpGet("/Sessions")]
+        [Authorize]
+        [ProducesResponseType(StatusCodes.Status200OK)]
+        public ActionResult<IEnumerable<SessionInfo>> GetSessions(
+            [FromQuery] Guid controllableByUserId,
+            [FromQuery] string deviceId,
+            [FromQuery] int? activeWithinSeconds)
+        {
+            var result = _sessionManager.Sessions;
+
+            if (!string.IsNullOrEmpty(deviceId))
+            {
+                result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
+            }
+
+            if (!controllableByUserId.Equals(Guid.Empty))
+            {
+                result = result.Where(i => i.SupportsRemoteControl);
+
+                var user = _userManager.GetUserById(controllableByUserId);
+
+                if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
+                {
+                    result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(controllableByUserId));
+                }
+
+                if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl))
+                {
+                    result = result.Where(i => !i.UserId.Equals(Guid.Empty));
+                }
+
+                if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0)
+                {
+                    var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value);
+                    result = result.Where(i => i.LastActivityDate >= minActiveDate);
+                }
+
+                result = result.Where(i =>
+                {
+                    if (!string.IsNullOrWhiteSpace(i.DeviceId))
+                    {
+                        if (!_deviceManager.CanAccessDevice(user, i.DeviceId))
+                        {
+                            return false;
+                        }
+                    }
+
+                    return true;
+                });
+            }
+
+            return Ok(result);
+        }
+
+        /// <summary>
+        /// Instructs a session to browse to an item or view.
+        /// </summary>
+        /// <param name="id">The session Id.</param>
+        /// <param name="itemType">The type of item to browse to.</param>
+        /// <param name="itemId">The Id of the item.</param>
+        /// <param name="itemName">The name of the item.</param>
+        /// <response code="204">Instruction sent to session.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/{id}/Viewing")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult DisplayContent(
+            [FromRoute] string id,
+            [FromQuery] string itemType,
+            [FromQuery] string itemId,
+            [FromQuery] string itemName)
+        {
+            var command = new BrowseRequest
+            {
+                ItemId = itemId,
+                ItemName = itemName,
+                ItemType = itemType
+            };
+
+            _sessionManager.SendBrowseCommand(
+                RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id,
+                id,
+                command,
+                CancellationToken.None);
+
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Instructs a session to play an item.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="itemIds">The ids of the items to play, comma delimited.</param>
+        /// <param name="startPositionTicks">The starting position of the first item.</param>
+        /// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
+        /// <param name="playRequest">The <see cref="PlayRequest"/>.</param>
+        /// <response code="204">Instruction sent to session.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/{id}/Playing")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult Play(
+            [FromRoute] string id,
+            [FromQuery] Guid[] itemIds,
+            [FromQuery] long? startPositionTicks,
+            [FromQuery] PlayCommand playCommand,
+            [FromBody, Required] PlayRequest playRequest)
+        {
+            if (playRequest == null)
+            {
+                throw new ArgumentException("Request Body may not be null");
+            }
+
+            playRequest.ItemIds = itemIds;
+            playRequest.StartPositionTicks = startPositionTicks;
+            playRequest.PlayCommand = playCommand;
+
+            _sessionManager.SendPlayCommand(
+                RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id,
+                id,
+                playRequest,
+                CancellationToken.None);
+
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Issues a playstate command to a client.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="playstateRequest">The <see cref="PlaystateRequest"/>.</param>
+        /// <response code="204">Playstate command sent to session.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/{id}/Playing/{command}")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult SendPlaystateCommand(
+            [FromRoute] string id,
+            [FromBody] PlaystateRequest playstateRequest)
+        {
+            _sessionManager.SendPlaystateCommand(
+                RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id,
+                id,
+                playstateRequest,
+                CancellationToken.None);
+
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Issues a system command to a client.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="command">The command to send.</param>
+        /// <response code="204">System command sent to session.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/{id}/System/{Command}")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult SendSystemCommand(
+            [FromRoute] string id,
+            [FromRoute] string command)
+        {
+            var name = command;
+            if (Enum.TryParse(name, true, out GeneralCommandType commandType))
+            {
+                name = commandType.ToString();
+            }
+
+            var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
+            var generalCommand = new GeneralCommand
+            {
+                Name = name,
+                ControllingUserId = currentSession.UserId
+            };
+
+            _sessionManager.SendGeneralCommand(currentSession.Id, id, generalCommand, CancellationToken.None);
+
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Issues a general command to a client.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="command">The command to send.</param>
+        /// <response code="204">General command sent to session.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/{id}/Command/{Command}")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult SendGeneralCommand(
+            [FromRoute] string id,
+            [FromRoute] string command)
+        {
+            var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
+
+            var generalCommand = new GeneralCommand
+            {
+                Name = command,
+                ControllingUserId = currentSession.UserId
+            };
+
+            _sessionManager.SendGeneralCommand(currentSession.Id, id, generalCommand, CancellationToken.None);
+
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Issues a full general command to a client.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="command">The <see cref="GeneralCommand"/>.</param>
+        /// <response code="204">Full general command sent to session.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/{id}/Command")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult SendFullGeneralCommand(
+            [FromRoute] string id,
+            [FromBody, Required] GeneralCommand command)
+        {
+            var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
+
+            if (command == null)
+            {
+                throw new ArgumentException("Request body may not be null");
+            }
+
+            command.ControllingUserId = currentSession.UserId;
+
+            _sessionManager.SendGeneralCommand(
+                currentSession.Id,
+                id,
+                command,
+                CancellationToken.None);
+
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Issues a command to a client to display a message to the user.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="text">The message test.</param>
+        /// <param name="header">The message header.</param>
+        /// <param name="timeoutMs">The message timeout. If omitted the user will have to confirm viewing the message.</param>
+        /// <response code="204">Message sent.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/{id}/Message")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult SendMessageCommand(
+            [FromRoute] string id,
+            [FromQuery] string text,
+            [FromQuery] string header,
+            [FromQuery] long? timeoutMs)
+        {
+            var command = new MessageCommand
+            {
+                Header = string.IsNullOrEmpty(header) ? "Message from Server" : header,
+                TimeoutMs = timeoutMs,
+                Text = text
+            };
+
+            _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, id, command, CancellationToken.None);
+
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Adds an additional user to a session.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="userId">The user id.</param>
+        /// <response code="204">User added to session.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/{id}/User/{userId}")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult AddUserToSession(
+            [FromRoute] string id,
+            [FromRoute] Guid userId)
+        {
+            _sessionManager.AddAdditionalUser(id, userId);
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Removes an additional user from a session.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="userId">The user id.</param>
+        /// <response code="204">User removed from session.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpDelete("/Sessions/{id}/User/{userId}")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult RemoveUserFromSession(
+            [FromRoute] string id,
+            [FromRoute] Guid userId)
+        {
+            _sessionManager.RemoveAdditionalUser(id, userId);
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Updates capabilities for a device.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="playableMediaTypes">A list of playable media types, comma delimited. Audio, Video, Book, Photo.</param>
+        /// <param name="supportedCommands">A list of supported remote control commands, comma delimited.</param>
+        /// <param name="supportsMediaControl">Determines whether media can be played remotely..</param>
+        /// <param name="supportsSync">Determines whether sync is supported.</param>
+        /// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param>
+        /// <response code="204">Capabilities posted.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/Capabilities")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult PostCapabilities(
+            [FromQuery] string id,
+            [FromQuery] string playableMediaTypes,
+            [FromQuery] string supportedCommands,
+            [FromQuery] bool supportsMediaControl,
+            [FromQuery] bool supportsSync,
+            [FromQuery] bool supportsPersistentIdentifier = true)
+        {
+            if (string.IsNullOrWhiteSpace(id))
+            {
+                id = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+            }
+
+            _sessionManager.ReportCapabilities(id, new ClientCapabilities
+            {
+                PlayableMediaTypes = RequestHelpers.Split(playableMediaTypes, ',', true),
+                SupportedCommands = RequestHelpers.Split(supportedCommands, ',', true),
+                SupportsMediaControl = supportsMediaControl,
+                SupportsSync = supportsSync,
+                SupportsPersistentIdentifier = supportsPersistentIdentifier
+            });
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Updates capabilities for a device.
+        /// </summary>
+        /// <param name="id">The session id.</param>
+        /// <param name="capabilities">The <see cref="ClientCapabilities"/>.</param>
+        /// <response code="204">Capabilities updated.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/Capabilities/Full")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult PostFullCapabilities(
+            [FromQuery] string id,
+            [FromBody, Required] ClientCapabilities capabilities)
+        {
+            if (string.IsNullOrWhiteSpace(id))
+            {
+                id = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+            }
+
+            _sessionManager.ReportCapabilities(id, capabilities);
+
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Reports that a session is viewing an item.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="itemId">The item id.</param>
+        /// <response code="204">Session reported to server.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/Viewing")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult ReportViewing(
+            [FromQuery] string sessionId,
+            [FromQuery] string itemId)
+        {
+            string session = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id;
+
+            _sessionManager.ReportNowViewingItem(session, itemId);
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Reports that a session has ended.
+        /// </summary>
+        /// <response code="204">Session end reported to server.</response>
+        /// <returns>A <see cref="NoContentResult"/>.</returns>
+        [HttpPost("/Sessions/Logout")]
+        [ProducesResponseType(StatusCodes.Status204NoContent)]
+        public ActionResult ReportSessionEnded()
+        {
+            AuthorizationInfo auth = _authContext.GetAuthorizationInfo(Request);
+
+            _sessionManager.Logout(auth.Token);
+            return NoContent();
+        }
+
+        /// <summary>
+        /// Get all auth providers.
+        /// </summary>
+        /// <response code="200">Auth providers retrieved.</response>
+        /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the auth providers.</returns>
+        [HttpGet("/Auth/Providers")]
+        [ProducesResponseType(StatusCodes.Status200OK)]
+        public ActionResult<IEnumerable<NameIdPair>> GetAuthProviders()
+        {
+            return _userManager.GetAuthenticationProviders();
+        }
+
+        /// <summary>
+        /// Get all password reset providers.
+        /// </summary>
+        /// <response code="200">Password reset providers retrieved.</response>
+        /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the password reset providers.</returns>
+        [HttpGet("/Auto/PasswordResetProviders")]
+        [ProducesResponseType(StatusCodes.Status200OK)]
+        public ActionResult<IEnumerable<NameIdPair>> GetPasswordResetProviders()
+        {
+            return _userManager.GetPasswordResetProviders();
+        }
+    }
+}

+ 21 - 0
Jellyfin.Api/Helpers/RequestHelpers.cs

@@ -1,6 +1,7 @@
 using System;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Session;
 using Microsoft.AspNetCore.Http;
 
 namespace Jellyfin.Api.Helpers
@@ -52,5 +53,25 @@ namespace Jellyfin.Api.Helpers
 
             return true;
         }
+
+        internal static SessionInfo GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request)
+        {
+            var authorization = authContext.GetAuthorizationInfo(request);
+            var user = authorization.User;
+            var session = sessionManager.LogSessionActivity(
+                authorization.Client,
+                authorization.Version,
+                authorization.DeviceId,
+                authorization.Device,
+                request.HttpContext.Connection.RemoteIpAddress.ToString(),
+                user);
+
+            if (session == null)
+            {
+                throw new ArgumentException("Session not found.");
+            }
+
+            return session;
+        }
     }
 }

+ 0 - 499
MediaBrowser.Api/Sessions/SessionService.cs

@@ -1,499 +0,0 @@
-using System;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Services;
-using MediaBrowser.Model.Session;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Api.Sessions
-{
-    /// <summary>
-    /// Class GetSessions.
-    /// </summary>
-    [Route("/Sessions", "GET", Summary = "Gets a list of sessions")]
-    [Authenticated]
-    public class GetSessions : IReturn<SessionInfo[]>
-    {
-        [ApiMember(Name = "ControllableByUserId", Description = "Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public Guid ControllableByUserId { get; set; }
-
-        [ApiMember(Name = "DeviceId", Description = "Filter by device Id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string DeviceId { get; set; }
-
-        public int? ActiveWithinSeconds { get; set; }
-    }
-
-    /// <summary>
-    /// Class DisplayContent.
-    /// </summary>
-    [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")]
-    [Authenticated]
-    public class DisplayContent : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-
-        /// <summary>
-        /// Artist, Genre, Studio, Person, or any kind of BaseItem
-        /// </summary>
-        /// <value>The type of the item.</value>
-        [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string ItemType { get; set; }
-
-        /// <summary>
-        /// Artist name, genre name, item Id, etc
-        /// </summary>
-        /// <value>The item identifier.</value>
-        [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string ItemId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the name of the item.
-        /// </summary>
-        /// <value>The name of the item.</value>
-        [ApiMember(Name = "ItemName", Description = "The name of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string ItemName { get; set; }
-    }
-
-    [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")]
-    [Authenticated]
-    public class Play : PlayRequest
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-    }
-
-    [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")]
-    [Authenticated]
-    public class SendPlaystateCommand : PlaystateRequest, IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-    }
-
-    [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")]
-    [Authenticated]
-    public class SendSystemCommand : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-
-        /// <summary>
-        /// Gets or sets the command.
-        /// </summary>
-        /// <value>The play command.</value>
-        [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Command { get; set; }
-    }
-
-    [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")]
-    [Authenticated]
-    public class SendGeneralCommand : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-
-        /// <summary>
-        /// Gets or sets the command.
-        /// </summary>
-        /// <value>The play command.</value>
-        [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Command { get; set; }
-    }
-
-    [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")]
-    [Authenticated]
-    public class SendFullGeneralCommand : GeneralCommand, IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-    }
-
-    [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")]
-    [Authenticated]
-    public class SendMessageCommand : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-
-        [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Text { get; set; }
-
-        [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Header { get; set; }
-
-        [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public long? TimeoutMs { get; set; }
-    }
-
-    [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")]
-    [Authenticated]
-    public class AddUserToSession : IReturnVoid
-    {
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-
-        [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string UserId { get; set; }
-    }
-
-    [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")]
-    [Authenticated]
-    public class RemoveUserFromSession : IReturnVoid
-    {
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string Id { get; set; }
-
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public string UserId { get; set; }
-    }
-
-    [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")]
-    [Authenticated]
-    public class PostCapabilities : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Id { get; set; }
-
-        [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string PlayableMediaTypes { get; set; }
-
-        [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string SupportedCommands { get; set; }
-
-        [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
-        public bool SupportsMediaControl { get; set; }
-
-        [ApiMember(Name = "SupportsSync", Description = "Determines whether sync is supported.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
-        public bool SupportsSync { get; set; }
-
-        [ApiMember(Name = "SupportsPersistentIdentifier", Description = "Determines whether the device supports a unique identifier.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
-        public bool SupportsPersistentIdentifier { get; set; }
-
-        public PostCapabilities()
-        {
-            SupportsPersistentIdentifier = true;
-        }
-    }
-
-    [Route("/Sessions/Capabilities/Full", "POST", Summary = "Updates capabilities for a device")]
-    [Authenticated]
-    public class PostFullCapabilities : ClientCapabilities, IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Id { get; set; }
-    }
-
-    [Route("/Sessions/Viewing", "POST", Summary = "Reports that a session is viewing an item")]
-    [Authenticated]
-    public class ReportViewing : IReturnVoid
-    {
-        [ApiMember(Name = "SessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string SessionId { get; set; }
-
-        [ApiMember(Name = "ItemId", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string ItemId { get; set; }
-    }
-
-    [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")]
-    [Authenticated]
-    public class ReportSessionEnded : IReturnVoid
-    {
-    }
-
-    [Route("/Auth/Providers", "GET")]
-    [Authenticated(Roles = "Admin")]
-    public class GetAuthProviders : IReturn<NameIdPair[]>
-    {
-    }
-
-    [Route("/Auth/PasswordResetProviders", "GET")]
-    [Authenticated(Roles = "Admin")]
-    public class GetPasswordResetProviders : IReturn<NameIdPair[]>
-    {
-    }
-
-    /// <summary>
-    /// Class SessionsService.
-    /// </summary>
-    public class SessionService : BaseApiService
-    {
-        /// <summary>
-        /// The session manager.
-        /// </summary>
-        private readonly ISessionManager _sessionManager;
-
-        private readonly IUserManager _userManager;
-        private readonly IAuthorizationContext _authContext;
-        private readonly IDeviceManager _deviceManager;
-        private readonly ISessionContext _sessionContext;
-
-        public SessionService(
-            ILogger<SessionService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory,
-            ISessionManager sessionManager,
-            IUserManager userManager,
-            IAuthorizationContext authContext,
-            IDeviceManager deviceManager,
-            ISessionContext sessionContext)
-            : base(logger, serverConfigurationManager, httpResultFactory)
-        {
-            _sessionManager = sessionManager;
-            _userManager = userManager;
-            _authContext = authContext;
-            _deviceManager = deviceManager;
-            _sessionContext = sessionContext;
-        }
-
-        public object Get(GetAuthProviders request)
-        {
-            return _userManager.GetAuthenticationProviders();
-        }
-
-        public object Get(GetPasswordResetProviders request)
-        {
-            return _userManager.GetPasswordResetProviders();
-        }
-
-        public void Post(ReportSessionEnded request)
-        {
-            var auth = _authContext.GetAuthorizationInfo(Request);
-
-            _sessionManager.Logout(auth.Token);
-        }
-
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public object Get(GetSessions request)
-        {
-            var result = _sessionManager.Sessions;
-
-            if (!string.IsNullOrEmpty(request.DeviceId))
-            {
-                result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase));
-            }
-
-            if (!request.ControllableByUserId.Equals(Guid.Empty))
-            {
-                result = result.Where(i => i.SupportsRemoteControl);
-
-                var user = _userManager.GetUserById(request.ControllableByUserId);
-
-                if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
-                {
-                    result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(request.ControllableByUserId));
-                }
-
-                if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl))
-                {
-                    result = result.Where(i => !i.UserId.Equals(Guid.Empty));
-                }
-
-                if (request.ActiveWithinSeconds.HasValue && request.ActiveWithinSeconds.Value > 0)
-                {
-                    var minActiveDate = DateTime.UtcNow.AddSeconds(0 - request.ActiveWithinSeconds.Value);
-                    result = result.Where(i => i.LastActivityDate >= minActiveDate);
-                }
-
-                result = result.Where(i =>
-                {
-                    var deviceId = i.DeviceId;
-
-                    if (!string.IsNullOrWhiteSpace(deviceId))
-                    {
-                        if (!_deviceManager.CanAccessDevice(user, deviceId))
-                        {
-                            return false;
-                        }
-                    }
-
-                    return true;
-                });
-            }
-
-            return ToOptimizedResult(result.ToArray());
-        }
-
-        public Task Post(SendPlaystateCommand request)
-        {
-            return _sessionManager.SendPlaystateCommand(GetSession(_sessionContext).Id, request.Id, request, CancellationToken.None);
-        }
-
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public Task Post(DisplayContent request)
-        {
-            var command = new BrowseRequest
-            {
-                ItemId = request.ItemId,
-                ItemName = request.ItemName,
-                ItemType = request.ItemType
-            };
-
-            return _sessionManager.SendBrowseCommand(GetSession(_sessionContext).Id, request.Id, command, CancellationToken.None);
-        }
-
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public Task Post(SendSystemCommand request)
-        {
-            var name = request.Command;
-            if (Enum.TryParse(name, true, out GeneralCommandType commandType))
-            {
-                name = commandType.ToString();
-            }
-
-            var currentSession = GetSession(_sessionContext);
-            var command = new GeneralCommand
-            {
-                Name = name,
-                ControllingUserId = currentSession.UserId
-            };
-
-            return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None);
-        }
-
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public Task Post(SendMessageCommand request)
-        {
-            var command = new MessageCommand
-            {
-                Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header,
-                TimeoutMs = request.TimeoutMs,
-                Text = request.Text
-            };
-
-            return _sessionManager.SendMessageCommand(GetSession(_sessionContext).Id, request.Id, command, CancellationToken.None);
-        }
-
-        /// <summary>
-        /// Posts the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        public Task Post(Play request)
-        {
-            return _sessionManager.SendPlayCommand(GetSession(_sessionContext).Id, request.Id, request, CancellationToken.None);
-        }
-
-        public Task Post(SendGeneralCommand request)
-        {
-            var currentSession = GetSession(_sessionContext);
-
-            var command = new GeneralCommand
-            {
-                Name = request.Command,
-                ControllingUserId = currentSession.UserId
-            };
-
-            return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None);
-        }
-
-        public Task Post(SendFullGeneralCommand request)
-        {
-            var currentSession = GetSession(_sessionContext);
-
-            request.ControllingUserId = currentSession.UserId;
-
-            return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None);
-        }
-
-        public void Post(AddUserToSession request)
-        {
-            _sessionManager.AddAdditionalUser(request.Id, new Guid(request.UserId));
-        }
-
-        public void Delete(RemoveUserFromSession request)
-        {
-            _sessionManager.RemoveAdditionalUser(request.Id, new Guid(request.UserId));
-        }
-
-        public void Post(PostCapabilities request)
-        {
-            if (string.IsNullOrWhiteSpace(request.Id))
-            {
-                request.Id = GetSession(_sessionContext).Id;
-            }
-
-            _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities
-            {
-                PlayableMediaTypes = SplitValue(request.PlayableMediaTypes, ','),
-                SupportedCommands = SplitValue(request.SupportedCommands, ','),
-                SupportsMediaControl = request.SupportsMediaControl,
-                SupportsSync = request.SupportsSync,
-                SupportsPersistentIdentifier = request.SupportsPersistentIdentifier
-            });
-        }
-
-        public void Post(PostFullCapabilities request)
-        {
-            if (string.IsNullOrWhiteSpace(request.Id))
-            {
-                request.Id = GetSession(_sessionContext).Id;
-            }
-
-            _sessionManager.ReportCapabilities(request.Id, request);
-        }
-
-        public void Post(ReportViewing request)
-        {
-            request.SessionId = GetSession(_sessionContext).Id;
-
-            _sessionManager.ReportNowViewingItem(request.SessionId, request.ItemId);
-        }
-    }
-}