Просмотр исходного кода

Display client version in the dashboard

Luke Pulverenti 12 лет назад
Родитель
Сommit
66624293ac

+ 13 - 5
MediaBrowser.Api/BaseApiService.cs

@@ -286,13 +286,21 @@ namespace MediaBrowser.Api
                     }
                 }
 
-                var deviceId = auth["DeviceId"];
-                var device = auth["Device"];
-                var client = auth["Client"];
+                string deviceId;
+                string device;
+                string client;
+                string version;
 
-                if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device))
+                auth.TryGetValue("DeviceId", out deviceId);
+                auth.TryGetValue("Device", out device);
+                auth.TryGetValue("Client", out client);
+                auth.TryGetValue("Version", out version);
+
+                version = version ?? "0.0.0.0";
+
+                if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version))
                 {
-                    SessionManager.LogConnectionActivity(client, deviceId, device, user);
+                    SessionManager.LogConnectionActivity(client, version, deviceId, device, user);
                 }
             }
         }

+ 75 - 1
MediaBrowser.Api/SessionsService.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Session;
@@ -106,6 +105,31 @@ namespace MediaBrowser.Api
         public PlayCommand PlayCommand { get; set; }
     }
 
+    [Route("/Sessions/{Id}/Playing/{Command}", "POST")]
+    [Api(("Issues a playstate command to a client"))]
+    public class SendPlaystateCommand : 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 Guid Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets the position to seek to
+        /// </summary>
+        [ApiMember(Name = "SeekPosition", Description = "The position to seek to.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public long? SeekPosition { get; set; }
+
+        /// <summary>
+        /// Gets or sets the play command.
+        /// </summary>
+        /// <value>The play command.</value>
+        [ApiMember(Name = "Command", Description = "The command to send - stop, pause, unpause, nexttrack, previoustrack, seek.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public PlaystateCommand Command { get; set; }
+    }
+
     /// <summary>
     /// Class SessionsService
     /// </summary>
@@ -142,6 +166,56 @@ namespace MediaBrowser.Api
             return ToOptimizedResult(result.Select(SessionInfoDtoBuilder.GetSessionInfoDto).ToList());
         }
 
+        public void Post(SendPlaystateCommand request)
+        {
+            var task = SendPlaystateCommand(request);
+
+            Task.WaitAll(task);
+        }
+
+        private async Task SendPlaystateCommand(SendPlaystateCommand request)
+        {
+            var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id);
+
+            if (session == null)
+            {
+                throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id));
+            }
+
+            if (!session.SupportsRemoteControl)
+            {
+                throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
+            }
+
+            var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+
+            if (socket != null)
+            {
+                try
+                {
+                    await socket.SendAsync(new WebSocketMessage<PlayStateRequest>
+                    {
+                        MessageType = "Playstate",
+
+                        Data = new PlayStateRequest
+                        {
+                            Command = request.Command,
+                            SeekPosition = request.SeekPosition
+                        }
+
+                    }, CancellationToken.None).ConfigureAwait(false);
+                }
+                catch (Exception ex)
+                {
+                    Logger.ErrorException("Error sending web socket message", ex);
+                }
+            }
+            else
+            {
+                throw new InvalidOperationException("The requested session does not have an open web socket.");
+            }
+        }
+        
         /// <summary>
         /// Posts the specified request.
         /// </summary>

+ 24 - 20
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -586,6 +586,25 @@ namespace MediaBrowser.Api.UserLibrary
             Task.WaitAll(task);
         }
 
+        private SessionInfo GetSession()
+        {
+            var auth = RequestFilterAttribute.GetAuthorization(RequestContext);
+
+            string deviceId;
+            string client;
+            string version;
+
+            auth.TryGetValue("DeviceId", out deviceId);
+            auth.TryGetValue("Client", out client);
+            auth.TryGetValue("Version", out version);
+
+            version = version ?? "0.0.0.0";
+
+            return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, deviceId) &&
+                string.Equals(i.Client, client) &&
+                string.Equals(i.ApplicationVersion, version));
+        }
+
         /// <summary>
         /// Posts the specified request.
         /// </summary>
@@ -596,12 +615,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
 
-            var auth = RequestFilterAttribute.GetAuthorization(RequestContext);
-
-            if (auth != null)
-            {
-                _sessionManager.OnPlaybackStart(user, item, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
-            }
+            _sessionManager.OnPlaybackStart(item, GetSession().Id);
         }
 
         /// <summary>
@@ -614,14 +628,9 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
 
-            var auth = RequestFilterAttribute.GetAuthorization(RequestContext);
-
-            if (auth != null)
-            {
-                var task = _sessionManager.OnPlaybackProgress(user, item, request.PositionTicks, request.IsPaused, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
+            var task = _sessionManager.OnPlaybackProgress(item, request.PositionTicks, request.IsPaused, GetSession().Id);
 
-                Task.WaitAll(task);
-            }
+            Task.WaitAll(task);
         }
 
         /// <summary>
@@ -634,14 +643,9 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
 
-            var auth = RequestFilterAttribute.GetAuthorization(RequestContext);
-
-            if (auth != null)
-            {
-                var task = _sessionManager.OnPlaybackStopped(user, item, request.PositionTicks, auth["Client"], auth["DeviceId"], auth["Device"] ?? string.Empty);
+            var task = _sessionManager.OnPlaybackStopped(item, request.PositionTicks, GetSession().Id);
 
-                Task.WaitAll(task);
-            }
+            Task.WaitAll(task);
         }
 
         /// <summary>

+ 2 - 2
MediaBrowser.Controller/Dto/DtoBuilder.cs

@@ -381,9 +381,9 @@ namespace MediaBrowser.Controller.Dto
 
                 if (parentWithImage != null)
                 {
-                    dto.ParentLogoItemId = GetClientItemId(parentWithImage);
+                    dto.ParentArtItemId = GetClientItemId(parentWithImage);
 
-                    dto.ParentLogoImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImage(ImageType.Art));
+                    dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImage(ImageType.Art));
                 }
             }
 

+ 2 - 1
MediaBrowser.Controller/Dto/SessionInfoDtoBuilder.cs

@@ -28,7 +28,8 @@ namespace MediaBrowser.Controller.Dto
                 NowViewingContext = session.NowViewingContext,
                 NowViewingItemId = session.NowViewingItemId,
                 NowViewingItemName = session.NowViewingItemName,
-                NowViewingItemType = session.NowViewingItemType
+                NowViewingItemType = session.NowViewingItemType,
+                ApplicationVersion = session.ApplicationVersion
             };
 
             if (session.NowPlayingItem != null)

+ 1 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -70,7 +70,7 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
-    <Compile Include="Configuration\IServerConfigurationManager.cs" />
+    <Compile Include="Notifications\Configuration\IServerConfigurationManager.cs" />
     <Compile Include="Dto\SessionInfoDtoBuilder.cs" />
     <Compile Include="Entities\Audio\MusicAlbumDisc.cs" />
     <Compile Include="Entities\Audio\MusicGenre.cs" />

+ 0 - 0
MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs → MediaBrowser.Controller/Notifications/Configuration/IServerConfigurationManager.cs


+ 9 - 16
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -36,49 +36,42 @@ namespace MediaBrowser.Controller.Session
         /// Logs the user activity.
         /// </summary>
         /// <param name="clientType">Type of the client.</param>
+        /// <param name="appVersion">The app version.</param>
         /// <param name="deviceId">The device id.</param>
         /// <param name="deviceName">Name of the device.</param>
         /// <param name="user">The user.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">user</exception>
-        Task LogConnectionActivity(string clientType, string deviceId, string deviceName, User user);
+        Task<SessionInfo> LogConnectionActivity(string clientType, string appVersion, string deviceId, string deviceName, User user);
 
         /// <summary>
         /// Used to report that playback has started for an item
         /// </summary>
-        /// <param name="user">The user.</param>
         /// <param name="item">The item.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="sessionId">The session id.</param>
+        /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
-        Task OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName);
+        Task OnPlaybackStart(BaseItem item, Guid sessionId);
 
         /// <summary>
         /// Used to report playback progress for an item
         /// </summary>
-        /// <param name="user">The user.</param>
         /// <param name="item">The item.</param>
         /// <param name="positionTicks">The position ticks.</param>
         /// <param name="isPaused">if set to <c>true</c> [is paused].</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="sessionId">The session id.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
-        Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, bool isPaused, string clientType, string deviceId, string deviceName);
+        Task OnPlaybackProgress(BaseItem item, long? positionTicks, bool isPaused, Guid sessionId);
 
         /// <summary>
         /// Used to report that playback has ended for an item
         /// </summary>
-        /// <param name="user">The user.</param>
         /// <param name="item">The item.</param>
         /// <param name="positionTicks">The position ticks.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="sessionId">The session id.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
-        Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName);
+        Task OnPlaybackStopped(BaseItem item, long? positionTicks, Guid sessionId);
     }
 }

+ 6 - 0
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -101,6 +101,12 @@ namespace MediaBrowser.Controller.Session
         /// <value>The web socket.</value>
         public List<IWebSocketConnection> WebSockets { get; set; }
 
+        /// <summary>
+        /// Gets or sets the application version.
+        /// </summary>
+        /// <value>The application version.</value>
+        public string ApplicationVersion { get; set; }
+
         /// <summary>
         /// Gets a value indicating whether this instance is active.
         /// </summary>

+ 5 - 2
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -268,6 +268,9 @@
     <Compile Include="..\MediaBrowser.Model\Querying\ThemeSongsResult.cs">
       <Link>Querying\ThemeSongsResult.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Querying\UserQuery.cs">
+      <Link>Querying\UserQuery.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Search\SearchHint.cs">
       <Link>Search\SearchHint.cs</Link>
     </Compile>
@@ -286,8 +289,8 @@
     <Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs">
       <Link>Session\PlayRequest.cs</Link>
     </Compile>
-    <Compile Include="..\MediaBrowser.Model\Session\PlaystateRequest.cs">
-      <Link>Session\PlaystateRequest.cs</Link>
+    <Compile Include="..\MediaBrowser.Model\Session\PlaystateCommand.cs">
+      <Link>Session\PlaystateCommand.cs</Link>
     </Compile>
     <Compile Include="..\MediaBrowser.Model\Session\SessionInfoDto.cs">
       <Link>Session\SessionInfoDto.cs</Link>

+ 14 - 0
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -100,6 +100,12 @@ namespace MediaBrowser.Model.ApiClient
         /// <returns>Task{UserDto[]}.</returns>
         Task<UserDto[]> GetUsersAsync();
 
+        /// <summary>
+        /// Gets the public users async.
+        /// </summary>
+        /// <returns>Task{UserDto[]}.</returns>
+        Task<UserDto[]> GetPublicUsersAsync();
+        
         /// <summary>
         /// Gets active client sessions.
         /// </summary>
@@ -358,6 +364,14 @@ namespace MediaBrowser.Model.ApiClient
         /// <returns>Task.</returns>
         Task SendBrowseCommandAsync(string sessionId, string itemId, string itemName, string itemType, string context);
 
+        /// <summary>
+        /// Sends the playstate command async.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="request">The request.</param>
+        /// <returns>Task.</returns>
+        Task SendPlaystateCommandAsync(string sessionId, PlayStateRequest request);
+
         /// <summary>
         /// Sends the play command async.
         /// </summary>

+ 2 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -72,9 +72,10 @@
     <Compile Include="Entities\BaseItemInfo.cs" />
     <Compile Include="Querying\NextUpQuery.cs" />
     <Compile Include="Querying\SimilarItemsQuery.cs" />
+    <Compile Include="Querying\UserQuery.cs" />
     <Compile Include="Session\BrowseRequest.cs" />
     <Compile Include="Session\PlayRequest.cs" />
-    <Compile Include="Session\PlaystateRequest.cs" />
+    <Compile Include="Session\PlaystateCommand.cs" />
     <Compile Include="Entities\ImageDownloadOptions.cs" />
     <Compile Include="Logging\ILogManager.cs" />
     <Compile Include="MediaInfo\BlurayDiscInfo.cs" />

+ 9 - 0
MediaBrowser.Model/Querying/UserQuery.cs

@@ -0,0 +1,9 @@
+
+namespace MediaBrowser.Model.Querying
+{
+    public class UserQuery
+    {
+        public bool? IsHidden { get; set; }
+        public bool? IsDisabled { get; set; }
+    }
+}

+ 7 - 19
MediaBrowser.Model/Session/PlaystateRequest.cs → MediaBrowser.Model/Session/PlaystateCommand.cs

@@ -1,25 +1,6 @@
 
 namespace MediaBrowser.Model.Session
 {
-    /// <summary>
-    /// Class PlaystateRequest
-    /// </summary>
-    public class PlaystateRequest
-    {
-        /// <summary>
-        /// Gets or sets the command.
-        /// </summary>
-        /// <value>The command.</value>
-        public PlaystateCommand Command { get; set; }
-
-        /// <summary>
-        /// Gets or sets the seek position.
-        /// Only applicable to seek commands.
-        /// </summary>
-        /// <value>The seek position.</value>
-        public long? SeekPosition { get; set; }
-    }
-
     /// <summary>
     /// Enum PlaystateCommand
     /// </summary>
@@ -50,4 +31,11 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         Seek
     }
+
+    public class PlayStateRequest
+    {
+        public PlaystateCommand Command { get; set; }
+
+        public long? SeekPosition { get; set; }
+    }
 }

+ 6 - 0
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -22,6 +22,12 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         /// <value>The name of the user.</value>
         public string UserName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the application version.
+        /// </summary>
+        /// <value>The application version.</value>
+        public string ApplicationVersion { get; set; }
         
         /// <summary>
         /// Gets or sets the type of the client.

+ 7 - 5
MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs

@@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
                 }
             }
         }
-        
+
         /// <summary>
         /// Creates the triggers that define when the task will run
         /// </summary>
@@ -148,13 +148,15 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
             var numComplete = 0;
 
-            var failHistoryPath = Path.Combine(_kernel.FFMpegManager.VideoImagesDataPath, "failures.json");
+            var failHistoryPath = Path.Combine(_kernel.FFMpegManager.VideoImagesDataPath, "failures.txt");
 
             List<string> previouslyFailedImages;
-            
+
             try
             {
-                previouslyFailedImages = _jsonSerializer.DeserializeFromFile<List<string>>(failHistoryPath);
+                previouslyFailedImages = File.ReadAllText(failHistoryPath)
+                    .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
+                    .ToList();
             }
             catch (FileNotFoundException)
             {
@@ -184,7 +186,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
                         Directory.CreateDirectory(parentPath);
                     }
 
-                    _jsonSerializer.SerializeToFile(previouslyFailedImages, failHistoryPath);
+                    File.WriteAllText(failHistoryPath, string.Join("|", previouslyFailedImages.ToArray()));
                 }
 
                 numComplete++;

+ 69 - 63
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -94,13 +94,32 @@ namespace MediaBrowser.Server.Implementations.Session
         /// Logs the user activity.
         /// </summary>
         /// <param name="clientType">Type of the client.</param>
+        /// <param name="appVersion">The app version.</param>
         /// <param name="deviceId">The device id.</param>
         /// <param name="deviceName">Name of the device.</param>
         /// <param name="user">The user.</param>
         /// <returns>Task.</returns>
+        /// <exception cref="System.UnauthorizedAccessException"></exception>
         /// <exception cref="System.ArgumentNullException">user</exception>
-        public Task LogConnectionActivity(string clientType, string deviceId, string deviceName, User user)
+        public async Task<SessionInfo> LogConnectionActivity(string clientType, string appVersion, string deviceId, string deviceName, User user)
         {
+            if (string.IsNullOrEmpty(clientType))
+            {
+                throw new ArgumentNullException("clientType");
+            }
+            if (string.IsNullOrEmpty(appVersion))
+            {
+                throw new ArgumentNullException("appVersion");
+            }
+            if (string.IsNullOrEmpty(deviceId))
+            {
+                throw new ArgumentNullException("deviceId");
+            }
+            if (string.IsNullOrEmpty(deviceName))
+            {
+                throw new ArgumentNullException("deviceName");
+            }
+
             if (user != null && user.Configuration.IsDisabled)
             {
                 throw new UnauthorizedAccessException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
@@ -108,11 +127,13 @@ namespace MediaBrowser.Server.Implementations.Session
 
             var activityDate = DateTime.UtcNow;
 
-            GetConnection(clientType, deviceId, deviceName, user).LastActivityDate = activityDate;
+            var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, user);
+            
+            session.LastActivityDate = activityDate;
 
             if (user == null)
             {
-                return _trueTaskResult;
+                return session;
             }
 
             var lastActivityDate = user.LastActivityDate;
@@ -122,50 +143,42 @@ namespace MediaBrowser.Server.Implementations.Session
             // Don't log in the db anymore frequently than 10 seconds
             if (lastActivityDate.HasValue && (activityDate - lastActivityDate.Value).TotalSeconds < 10)
             {
-                return _trueTaskResult;
+                return session;
             }
 
             // Save this directly. No need to fire off all the events for this.
-            return _userRepository.SaveUser(user, CancellationToken.None);
+            await _userRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
+
+            return session;
         }
 
         /// <summary>
         /// Updates the now playing item id.
         /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="session">The session.</param>
         /// <param name="item">The item.</param>
         /// <param name="isPaused">if set to <c>true</c> [is paused].</param>
         /// <param name="currentPositionTicks">The current position ticks.</param>
-        private void UpdateNowPlayingItemId(User user, string clientType, string deviceId, string deviceName, BaseItem item, bool isPaused, long? currentPositionTicks = null)
+        private void UpdateNowPlayingItem(SessionInfo session, BaseItem item, bool isPaused, long? currentPositionTicks = null)
         {
-            var conn = GetConnection(clientType, deviceId, deviceName, user);
-
-            conn.IsPaused = isPaused;
-            conn.NowPlayingPositionTicks = currentPositionTicks;
-            conn.NowPlayingItem = item;
-            conn.LastActivityDate = DateTime.UtcNow;
+            session.IsPaused = isPaused;
+            session.NowPlayingPositionTicks = currentPositionTicks;
+            session.NowPlayingItem = item;
+            session.LastActivityDate = DateTime.UtcNow;
         }
 
         /// <summary>
         /// Removes the now playing item id.
         /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="session">The session.</param>
         /// <param name="item">The item.</param>
-        private void RemoveNowPlayingItemId(User user, string clientType, string deviceId, string deviceName, BaseItem item)
+        private void RemoveNowPlayingItem(SessionInfo session, BaseItem item)
         {
-            var conn = GetConnection(clientType, deviceId, deviceName, user);
-
-            if (conn.NowPlayingItem != null && conn.NowPlayingItem.Id == item.Id)
+            if (session.NowPlayingItem != null && session.NowPlayingItem.Id == item.Id)
             {
-                conn.NowPlayingItem = null;
-                conn.NowPlayingPositionTicks = null;
-                conn.IsPaused = null;
+                session.NowPlayingItem = null;
+                session.NowPlayingPositionTicks = null;
+                session.IsPaused = null;
             }
         }
 
@@ -173,23 +186,24 @@ namespace MediaBrowser.Server.Implementations.Session
         /// Gets the connection.
         /// </summary>
         /// <param name="clientType">Type of the client.</param>
+        /// <param name="appVersion">The app version.</param>
         /// <param name="deviceId">The device id.</param>
         /// <param name="deviceName">Name of the device.</param>
         /// <param name="user">The user.</param>
         /// <returns>SessionInfo.</returns>
-        private SessionInfo GetConnection(string clientType, string deviceId, string deviceName, User user)
+        private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, User user)
         {
-            var key = clientType + deviceId;
+            var key = clientType + deviceId + appVersion;
 
             var connection = _activeConnections.GetOrAdd(key, keyName => new SessionInfo
             {
                 Client = clientType,
                 DeviceId = deviceId,
+                ApplicationVersion = appVersion,
                 Id = Guid.NewGuid()
             });
 
             connection.DeviceName = deviceName;
-
             connection.User = user;
 
             return connection;
@@ -198,28 +212,25 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <summary>
         /// Used to report that playback has started for an item
         /// </summary>
-        /// <param name="user">The user.</param>
         /// <param name="item">The item.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
-        /// <exception cref="System.ArgumentNullException">
-        /// </exception>
-        public async Task OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName)
+        /// <param name="sessionId">The session id.</param>
+        /// <returns>Task.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public async Task OnPlaybackStart(BaseItem item, Guid sessionId)
         {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
             if (item == null)
             {
                 throw new ArgumentNullException();
             }
 
-            UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item, false);
+            var session = Sessions.First(i => i.Id.Equals(sessionId));
+
+            UpdateNowPlayingItem(session, item, false);
 
             var key = item.GetUserDataKey();
 
+            var user = session.User;
+            
             var data = _userDataRepository.GetUserData(user.Id, key);
 
             data.PlayCount++;
@@ -248,27 +259,25 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <param name="item">The item.</param>
         /// <param name="positionTicks">The position ticks.</param>
         /// <param name="isPaused">if set to <c>true</c> [is paused].</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="sessionId">The session id.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">
         /// </exception>
-        public async Task OnPlaybackProgress(User user, BaseItem item, long? positionTicks, bool isPaused, string clientType, string deviceId, string deviceName)
+        public async Task OnPlaybackProgress(BaseItem item, long? positionTicks, bool isPaused, Guid sessionId)
         {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
             if (item == null)
             {
                 throw new ArgumentNullException();
             }
 
-            UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item, isPaused, positionTicks);
+            var session = Sessions.First(i => i.Id.Equals(sessionId));
+
+            UpdateNowPlayingItem(session, item, isPaused, positionTicks);
 
             var key = item.GetUserDataKey();
 
+            var user = session.User;
+
             if (positionTicks.HasValue)
             {
                 var data = _userDataRepository.GetUserData(user.Id, key);
@@ -282,36 +291,33 @@ namespace MediaBrowser.Server.Implementations.Session
                 Item = item,
                 User = user,
                 PlaybackPositionTicks = positionTicks
+
             }, _logger);
         }
 
         /// <summary>
         /// Used to report that playback has ended for an item
         /// </summary>
-        /// <param name="user">The user.</param>
         /// <param name="item">The item.</param>
         /// <param name="positionTicks">The position ticks.</param>
-        /// <param name="clientType">Type of the client.</param>
-        /// <param name="deviceId">The device id.</param>
-        /// <param name="deviceName">Name of the device.</param>
+        /// <param name="sessionId">The session id.</param>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException">
-        /// </exception>
-        public async Task OnPlaybackStopped(User user, BaseItem item, long? positionTicks, string clientType, string deviceId, string deviceName)
+        /// <exception cref="System.ArgumentNullException"></exception>
+        public async Task OnPlaybackStopped(BaseItem item, long? positionTicks, Guid sessionId)
         {
-            if (user == null)
-            {
-                throw new ArgumentNullException();
-            }
             if (item == null)
             {
                 throw new ArgumentNullException();
             }
 
-            RemoveNowPlayingItemId(user, clientType, deviceId, deviceName, item);
+            var session = Sessions.First(i => i.Id.Equals(sessionId));
+
+            RemoveNowPlayingItem(session, item);
 
             var key = item.GetUserDataKey();
 
+            var user = session.User;
+            
             var data = _userDataRepository.GetUserData(user.Id, key);
 
             if (positionTicks.HasValue)

+ 3 - 3
MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -103,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.Session
                 {
                     var item = DtoBuilder.GetItemByClientId(message.Data, _userManager, _libraryManager);
 
-                    _sessionManager.OnPlaybackStart(_userManager.GetUserById(session.User.Id), item, session.Client, session.DeviceId, session.DeviceName);
+                    _sessionManager.OnPlaybackStart(item, session.Id);
                 }
             }
             else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase))
@@ -130,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Session
 
                     var isPaused = vals.Length > 2 && string.Equals(vals[2], "true", StringComparison.OrdinalIgnoreCase);
 
-                    _sessionManager.OnPlaybackProgress(_userManager.GetUserById(session.User.Id), item, positionTicks, isPaused, session.Client, session.DeviceId, session.DeviceName);
+                    _sessionManager.OnPlaybackProgress(item, positionTicks, isPaused, session.Id);
                 }
             }
             else if (string.Equals(message.MessageType, "PlaybackStopped", StringComparison.OrdinalIgnoreCase))
@@ -155,7 +155,7 @@ namespace MediaBrowser.Server.Implementations.Session
                         }
                     }
 
-                    _sessionManager.OnPlaybackStopped(_userManager.GetUserById(session.User.Id), item, positionTicks, session.Client, session.DeviceId, session.DeviceName);
+                    _sessionManager.OnPlaybackStopped(item, positionTicks, session.Id);
                 }
             }
 

+ 6 - 0
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -512,6 +512,12 @@ namespace MediaBrowser.WebDashboard.Api
 
             var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
 
+            var versionString = string.Format("window.dashboardVersion='{0}';", GetType().Assembly.GetName().Version);
+            var versionBytes = Encoding.UTF8.GetBytes(versionString);
+
+            await memoryStream.WriteAsync(versionBytes, 0, versionBytes.Length).ConfigureAwait(false);
+            await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
+
             await AppendResource(memoryStream, "thirdparty/autoNumeric.js", newLineBytes).ConfigureAwait(false);
             await AppendResource(memoryStream, "thirdparty/html5slider.js", newLineBytes).ConfigureAwait(false);
 

+ 5 - 4
MediaBrowser.WebDashboard/ApiClient.js

@@ -10,8 +10,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
      * @param {String} serverHostName
      * @param {String} serverPortNumber
      * @param {String} clientName 
+     * @param {String} applicationVersion 
      */
-    return function (serverProtocol, serverHostName, serverPortNumber, clientName) {
+    return function (serverProtocol, serverHostName, serverPortNumber, clientName, applicationVersion) {
 
         if (!serverProtocol) {
             throw new Error("Must supply a serverProtocol, e.g. http:");
@@ -120,7 +121,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
 
             if (clientName) {
 
-                var auth = 'MediaBrowser Client="' + clientName + '", Device="' + deviceName + '", DeviceId="' + deviceId + '"';
+                var auth = 'MediaBrowser Client="' + clientName + '", Device="' + deviceName + '", DeviceId="' + deviceId + '", Version="' + applicationVersion + '"';
 
                 if (currentUserId) {
                     auth += ', UserId="' + currentUserId + '"';
@@ -3277,11 +3278,11 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
 /**
  * Provides a friendly way to create an api client instance using information from the browser's current url
  */
-MediaBrowser.ApiClient.create = function (clientName) {
+MediaBrowser.ApiClient.create = function (clientName, applicationVersion) {
 
     var loc = window.location;
 
-    return new MediaBrowser.ApiClient(loc.protocol, loc.hostname, loc.port, clientName);
+    return new MediaBrowser.ApiClient(loc.protocol, loc.hostname, loc.port, clientName, applicationVersion);
 };
 
 /**

+ 1 - 1
MediaBrowser.WebDashboard/packages.config

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.139" targetFramework="net45" />
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.141" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.54" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.54" targetFramework="net45" />
 </packages>