Jelajahi Sumber

fixes #555 - Have clients report seek and queuing capabilities

Luke Pulverenti 11 tahun lalu
induk
melakukan
b49764dbaa
23 mengubah file dengan 248 tambahan dan 116 penghapusan
  1. 35 2
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  2. 7 11
      MediaBrowser.Common.Implementations/Logging/NlogManager.cs
  3. 18 19
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
  4. 5 1
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs
  5. 2 3
      MediaBrowser.Controller/Entities/Folder.cs
  6. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  7. 3 4
      MediaBrowser.Controller/Session/ISessionManager.cs
  8. 38 0
      MediaBrowser.Controller/Session/PlaybackInfo.cs
  9. 13 0
      MediaBrowser.Controller/Session/SessionInfo.cs
  10. 3 1
      MediaBrowser.Model/ApiClient/IApiClient.cs
  11. 15 2
      MediaBrowser.Model/Session/SessionInfoDto.cs
  12. 0 1
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  13. 3 1
      MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs
  14. 3 1
      MediaBrowser.Providers/TV/SeriesPostScanTask.cs
  15. 3 1
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  16. 16 38
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  17. 3 1
      MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs
  18. 7 8
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  19. 15 7
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  20. 42 10
      MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
  21. 3 1
      MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs
  22. 12 3
      MediaBrowser.WebDashboard/ApiClient.js
  23. 1 1
      MediaBrowser.WebDashboard/packages.config

+ 35 - 2
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -186,7 +186,7 @@ namespace MediaBrowser.Api.UserLibrary
 
         [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public DateTime? DatePlayed { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the id.
         /// </summary>
@@ -224,6 +224,13 @@ namespace MediaBrowser.Api.UserLibrary
     [Api(Description = "Reports that a user has begun playing an item")]
     public class OnPlaybackStart : IReturnVoid
     {
+        public OnPlaybackStart()
+        {
+            // Have to default these until all clients have a chance to incorporate them
+            CanSeek = true;
+            QueueableMediaTypes = "Audio,Video,Book,Game";
+        }
+
         /// <summary>
         /// Gets or sets the user id.
         /// </summary>
@@ -237,6 +244,20 @@ namespace MediaBrowser.Api.UserLibrary
         /// <value>The id.</value>
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         public string Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes.
+        /// </summary>
+        /// <value><c>true</c> if likes; otherwise, <c>false</c>.</value>
+        [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
+        public bool CanSeek { get; set; }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        [ApiMember(Name = "QueueableMediaTypes", Description = "A list of media types that can be queued from this item, comma delimited. Audio,Video,Book,Game", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
+        public string QueueableMediaTypes { get; set; }
     }
 
     /// <summary>
@@ -378,6 +399,8 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
         /// <param name="itemRepo">The item repo.</param>
+        /// <param name="sessionManager">The session manager.</param>
+        /// <param name="dtoService">The dto service.</param>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
         public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo, ISessionManager sessionManager, IDtoService dtoService)
         {
@@ -665,7 +688,17 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = _dtoService.GetItemByDtoId(request.Id, user.Id);
 
-            _sessionManager.OnPlaybackStart(item, GetSession().Id);
+            var queueableMediaTypes = (request.QueueableMediaTypes ?? string.Empty);
+
+            var info = new PlaybackInfo
+            {
+                CanSeek = request.CanSeek,
+                Item = item,
+                SessionId = GetSession().Id,
+                QueueableMediaTypes = queueableMediaTypes.Split(',').ToList()
+            };
+
+            _sessionManager.OnPlaybackStart(info);
         }
 
         /// <summary>

+ 7 - 11
MediaBrowser.Common.Implementations/Logging/NlogManager.cs

@@ -5,7 +5,6 @@ using NLog.Targets;
 using System;
 using System.IO;
 using System.Linq;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Common.Implementations.Logging
 {
@@ -193,17 +192,14 @@ namespace MediaBrowser.Common.Implementations.Logging
 
             if (LoggerLoaded != null)
             {
-                Task.Run(() =>
+                try
                 {
-                    try
-                    {
-                        LoggerLoaded(this, EventArgs.Empty);
-                    }
-                    catch (Exception ex)
-                    {
-                        GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex);
-                    }
-                });
+                    LoggerLoaded(this, EventArgs.Empty);
+                }
+                catch (Exception ex)
+                {
+                    GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex);
+                }
             }
         }
     }

+ 18 - 19
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs

@@ -54,33 +54,32 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         /// <returns>Task.</returns>
         public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            return Task.Run(() =>
-            {
-                // Delete log files more than n days old
-                var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays));
+            // Delete log files more than n days old
+            var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays));
+
+            var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
+                          .Where(f => f.LastWriteTimeUtc < minDateModified)
+                          .ToList();
 
-                var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
-                              .Where(f => f.LastWriteTimeUtc < minDateModified)
-                              .ToList();
+            var index = 0;
 
-                var index = 0;
+            foreach (var file in filesToDelete)
+            {
+                double percent = index;
+                percent /= filesToDelete.Count;
 
-                foreach (var file in filesToDelete)
-                {
-                    double percent = index;
-                    percent /= filesToDelete.Count;
+                progress.Report(100 * percent);
 
-                    progress.Report(100 * percent);
+                cancellationToken.ThrowIfCancellationRequested();
 
-                    cancellationToken.ThrowIfCancellationRequested();
+                File.Delete(file.FullName);
 
-                    File.Delete(file.FullName);
+                index++;
+            }
 
-                    index++;
-                }
+            progress.Report(100);
 
-                progress.Report(100);
-            });
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 5 - 1
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs

@@ -58,7 +58,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
 
             progress.Report(0);
 
-            return Task.Run(() => LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info));
+            LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging
+                                        ? LogSeverity.Debug
+                                        : LogSeverity.Info);
+
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 2 - 3
MediaBrowser.Controller/Entities/Folder.cs

@@ -16,7 +16,6 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
-using MoreLinq;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -690,7 +689,7 @@ namespace MediaBrowser.Controller.Entities
 
             var options = new ParallelOptions
             {
-                MaxDegreeOfParallelism = 20
+                MaxDegreeOfParallelism = 10
             };
 
             Parallel.ForEach(nonCachedChildren, options, child =>
@@ -805,7 +804,7 @@ namespace MediaBrowser.Controller.Entities
 
             foreach (var tuple in list)
             {
-                if (tasks.Count > 8)
+                if (tasks.Count > 5)
                 {
                     await Task.WhenAll(tasks).ConfigureAwait(false);
                 }

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

@@ -165,6 +165,7 @@
     <Compile Include="Kernel.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Providers\BaseMetadataProvider.cs" />
+    <Compile Include="Session\PlaybackInfo.cs" />
     <Compile Include="Session\SessionInfo.cs" />
     <Compile Include="Sorting\IBaseItemComparer.cs" />
     <Compile Include="Sorting\IUserBaseItemComparer.cs" />

+ 3 - 4
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -47,11 +47,9 @@ namespace MediaBrowser.Controller.Session
         /// <summary>
         /// Used to report that playback has started for an item
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="sessionId">The session id.</param>
+        /// <param name="info">The info.</param>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        Task OnPlaybackStart(BaseItem item, Guid sessionId);
+        Task OnPlaybackStart(PlaybackInfo info);
 
         /// <summary>
         /// Used to report playback progress for an item
@@ -59,6 +57,7 @@ namespace MediaBrowser.Controller.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="isMuted">if set to <c>true</c> [is muted].</param>
         /// <param name="sessionId">The session id.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>

+ 38 - 0
MediaBrowser.Controller/Session/PlaybackInfo.cs

@@ -0,0 +1,38 @@
+using MediaBrowser.Controller.Entities;
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Session
+{
+    public class PlaybackInfo
+    {
+        public PlaybackInfo()
+        {
+            QueueableMediaTypes = new List<string>();
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance can seek.
+        /// </summary>
+        /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
+        public bool CanSeek { get; set; }
+
+        /// <summary>
+        /// Gets or sets the queueable media types.
+        /// </summary>
+        /// <value>The queueable media types.</value>
+        public List<string> QueueableMediaTypes { get; set; }
+
+        /// <summary>
+        /// Gets or sets the item.
+        /// </summary>
+        /// <value>The item.</value>
+        public BaseItem Item { get; set; }
+
+        /// <summary>
+        /// Gets or sets the session id.
+        /// </summary>
+        /// <value>The session id.</value>
+        public Guid SessionId { get; set; }
+    }
+}

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

@@ -15,8 +15,21 @@ namespace MediaBrowser.Controller.Session
         public SessionInfo()
         {
             WebSockets = new List<IWebSocketConnection>();
+            QueueableMediaTypes = new List<string>();
         }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance can seek.
+        /// </summary>
+        /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
+        public bool CanSeek { get; set; }
+
+        /// <summary>
+        /// Gets or sets the queueable media types.
+        /// </summary>
+        /// <value>The queueable media types.</value>
+        public List<string> QueueableMediaTypes { get; set; }
+
         /// <summary>
         /// Gets or sets the id.
         /// </summary>

+ 3 - 1
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -497,9 +497,11 @@ namespace MediaBrowser.Model.ApiClient
         /// </summary>
         /// <param name="itemId">The item id.</param>
         /// <param name="userId">The user id.</param>
+        /// <param name="isSeekable">if set to <c>true</c> [is seekable].</param>
+        /// <param name="queueableMediaTypes">The list of media types that the client is capable of queuing onto the playlist. See MediaType class.</param>
         /// <returns>Task{UserItemDataDto}.</returns>
         /// <exception cref="ArgumentNullException">itemId</exception>
-        Task ReportPlaybackStartAsync(string itemId, string userId);
+        Task ReportPlaybackStartAsync(string itemId, string userId, bool isSeekable, List<string> queueableMediaTypes);
 
         /// <summary>
         /// Reports playback progress to the server

+ 15 - 2
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -1,11 +1,24 @@
-using System.ComponentModel;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Entities;
 using System;
+using System.Collections.Generic;
+using System.ComponentModel;
 
 namespace MediaBrowser.Model.Session
 {
     public class SessionInfoDto : INotifyPropertyChanged
     {
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance can seek.
+        /// </summary>
+        /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
+        public bool CanSeek { get; set; }
+
+        /// <summary>
+        /// Gets or sets the queueable media types.
+        /// </summary>
+        /// <value>The queueable media types.</value>
+        public List<string> QueueableMediaTypes { get; set; }
+        
         /// <summary>
         /// Gets or sets the id.
         /// </summary>

+ 0 - 1
MediaBrowser.Providers/Movies/FanArtMovieProvider.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;

+ 3 - 1
MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs

@@ -23,7 +23,9 @@ namespace MediaBrowser.Providers.Music
 
         public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            return Task.Run(() => RunInternal(progress, cancellationToken));
+            RunInternal(progress, cancellationToken);
+
+            return Task.FromResult(true);
         }
 
         private void RunInternal(IProgress<double> progress, CancellationToken cancellationToken)

+ 3 - 1
MediaBrowser.Providers/TV/SeriesPostScanTask.cs

@@ -21,7 +21,9 @@ namespace MediaBrowser.Providers.TV
 
         public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            return Task.Run(() => RunInternal(progress, cancellationToken));
+            RunInternal(progress, cancellationToken);
+
+            return Task.FromResult(true);
         }
 
         private void RunInternal(IProgress<double> progress, CancellationToken cancellationToken)

+ 3 - 1
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -237,7 +237,9 @@ namespace MediaBrowser.Server.Implementations.Dto
                 NowViewingItemId = session.NowViewingItemId,
                 NowViewingItemName = session.NowViewingItemName,
                 NowViewingItemType = session.NowViewingItemType,
-                ApplicationVersion = session.ApplicationVersion
+                ApplicationVersion = session.ApplicationVersion,
+                CanSeek = session.CanSeek,
+                QueueableMediaTypes = session.QueueableMediaTypes
             };
 
             if (session.NowPlayingItem != null)

+ 16 - 38
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -829,10 +829,6 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <returns>Task.</returns>
         public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            const int maxTasks = 3;
-
-            var tasks = new List<Task>();
-
             var people = RootFolder.RecursiveChildren
                 .SelectMany(c => c.People)
                 .DistinctBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
@@ -842,47 +838,27 @@ namespace MediaBrowser.Server.Implementations.Library
 
             foreach (var person in people)
             {
-                if (tasks.Count > maxTasks)
+                cancellationToken.ThrowIfCancellationRequested();
+
+                try
                 {
-                    await Task.WhenAll(tasks).ConfigureAwait(false);
-                    tasks.Clear();
+                    var item = GetPerson(person.Name);
 
-                    // Safe cancellation point, when there are no pending tasks
-                    cancellationToken.ThrowIfCancellationRequested();
+                    await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
                 }
-
-                // Avoid accessing the foreach variable within the closure
-                var currentPerson = person;
-
-                tasks.Add(Task.Run(async () =>
+                catch (IOException ex)
                 {
-                    cancellationToken.ThrowIfCancellationRequested();
-
-                    try
-                    {
-                        var item = GetPerson(currentPerson.Name);
-
-                        await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
-                    }
-                    catch (IOException ex)
-                    {
-                        _logger.ErrorException("Error validating IBN entry {0}", ex, currentPerson.Name);
-                    }
+                    _logger.ErrorException("Error validating IBN entry {0}", ex, person.Name);
+                }
 
-                    // Update progress
-                    lock (progress)
-                    {
-                        numComplete++;
-                        double percent = numComplete;
-                        percent /= people.Count;
+                // Update progress
+                numComplete++;
+                double percent = numComplete;
+                percent /= people.Count;
 
-                        progress.Report(100 * percent);
-                    }
-                }));
+                progress.Report(100 * percent);
             }
 
-            await Task.WhenAll(tasks).ConfigureAwait(false);
-
             progress.Report(100);
 
             _logger.Info("People validation complete");
@@ -956,7 +932,9 @@ namespace MediaBrowser.Server.Implementations.Library
         public Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken)
         {
             // Just run the scheduled task so that the user can see it
-            return Task.Run(() => _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>());
+            _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
+
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 3 - 1
MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs

@@ -41,7 +41,9 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
         /// <returns>Task.</returns>
         public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            return Task.Run(() => RunInternal(progress, cancellationToken));
+            RunInternal(progress, cancellationToken);
+
+            return Task.FromResult(true);
         }
 
         private void RunInternal(IProgress<double> progress, CancellationToken cancellationToken)

+ 7 - 8
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -333,17 +333,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
         /// <returns>Task.</returns>
         public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)
         {
-            return Task.Run(() =>
+            if (!Directory.Exists(_criticReviewsPath))
             {
-                if (!Directory.Exists(_criticReviewsPath))
-                {
-                    Directory.CreateDirectory(_criticReviewsPath);
-                }
+                Directory.CreateDirectory(_criticReviewsPath);
+            }
 
-                var path = Path.Combine(_criticReviewsPath, itemId + ".json");
+            var path = Path.Combine(_criticReviewsPath, itemId + ".json");
+
+            _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
 
-                _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
-            });
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 15 - 7
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -207,21 +207,29 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <summary>
         /// Used to report that playback has started for an item
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="sessionId">The session id.</param>
+        /// <param name="info">The info.</param>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public async Task OnPlaybackStart(BaseItem item, Guid sessionId)
+        /// <exception cref="System.ArgumentNullException">info</exception>
+        public async Task OnPlaybackStart(PlaybackInfo info)
         {
-            if (item == null)
+            if (info == null)
             {
-                throw new ArgumentNullException();
+                throw new ArgumentNullException("info");
+            }
+            if (info.SessionId == Guid.Empty)
+            {
+                throw new ArgumentNullException("info");
             }
 
-            var session = Sessions.First(i => i.Id.Equals(sessionId));
+            var session = Sessions.First(i => i.Id.Equals(info.SessionId));
+
+            var item = info.Item;
 
             UpdateNowPlayingItem(session, item, false, false);
 
+            session.CanSeek = info.CanSeek;
+            session.QueueableMediaTypes = info.QueueableMediaTypes;
+
             var key = item.GetUserDataKey();
 
             var user = session.User;

+ 42 - 10
MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -101,16 +101,7 @@ namespace MediaBrowser.Server.Implementations.Session
             }
             else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase))
             {
-                _logger.Debug("Received PlaybackStart message");
-                
-                var session = _sessionManager.Sessions.FirstOrDefault(i => i.WebSockets.Contains(message.Connection));
-
-                if (session != null && session.User != null)
-                {
-                    var item = _dtoService.GetItemByDtoId(message.Data);
-
-                    _sessionManager.OnPlaybackStart(item, session.Id);
-                }
+                ReportPlaybackStart(message);
             }
             else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase))
             {
@@ -170,5 +161,46 @@ namespace MediaBrowser.Server.Implementations.Session
 
             return _trueTaskResult;
         }
+
+        /// <summary>
+        /// Reports the playback start.
+        /// </summary>
+        /// <param name="message">The message.</param>
+        private void ReportPlaybackStart(WebSocketMessageInfo message)
+        {
+            _logger.Debug("Received PlaybackStart message");
+            
+            var session = _sessionManager.Sessions
+                .FirstOrDefault(i => i.WebSockets.Contains(message.Connection));
+
+            if (session != null && session.User != null)
+            {
+                var vals = message.Data.Split('|');
+
+                var item = _dtoService.GetItemByDtoId(vals[0]);
+
+                var queueableMediaTypes = string.Empty;
+                var canSeek = true;
+
+                if (vals.Length > 1)
+                {
+                    canSeek = string.Equals(vals[1], "true", StringComparison.OrdinalIgnoreCase);
+                }
+                if (vals.Length > 2)
+                {
+                    queueableMediaTypes = vals[2];
+                }
+  
+                var info = new PlaybackInfo
+                {
+                    CanSeek = canSeek,
+                    Item = item,
+                    SessionId = session.Id,
+                    QueueableMediaTypes = queueableMediaTypes.Split(',').ToList()
+                };
+
+                _sessionManager.OnPlaybackStart(info);
+            }
+        }
     }
 }

+ 3 - 1
MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs

@@ -92,7 +92,9 @@ namespace MediaBrowser.Server.Implementations.WebSocket
         /// <returns>Task.</returns>
         public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
         {
-            return Task.Run(() => UserContext.Send(bytes));
+            UserContext.Send(bytes);
+
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 12 - 3
MediaBrowser.WebDashboard/ApiClient.js

@@ -3200,7 +3200,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
          * @param {String} userId
          * @param {String} itemId
          */
-        self.reportPlaybackStart = function (userId, itemId) {
+        self.reportPlaybackStart = function (userId, itemId, canSeek, queueableMediaTypes) {
 
             if (!userId) {
                 throw new Error("null userId");
@@ -3210,17 +3210,26 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
                 throw new Error("null itemId");
             }
 
+            canSeek = canSeek || false;
+            queueableMediaTypes = queueableMediaTypes || '';
+
             if (self.isWebSocketOpen()) {
 
                 var deferred = $.Deferred();
 
-                self.sendWebSocketMessage("PlaybackStart", itemId);
+                var msg = [itemId, canSeek, queueableMediaTypes];
+
+                self.sendWebSocketMessage("PlaybackStart", msg.join('|'));
 
                 deferred.resolveWith(null, []);
                 return deferred.promise();
             }
 
-            var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId);
+            var url = self.getUrl("Users/" + userId + "/PlayingItems/" + itemId, {
+
+                CanSeek: canSeek,
+                QueueableMediaTypes: queueableMediaTypes
+            });
 
             return self.ajax({
                 type: "POST",

+ 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.175" targetFramework="net45" />
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.176" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" />
 </packages>