Browse Source

fixes #555 - Have clients report seek and queuing capabilities

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
b49764dbaa
23 tập tin đã thay đổi với 248 bổ sung116 xóa
  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>