Browse Source

Address requested changes and fix some warnings

Ionut Andrei Oanca 4 years ago
parent
commit
1dbc91978e
44 changed files with 965 additions and 985 deletions
  1. 125 126
      Emby.Server.Implementations/SyncPlay/GroupController.cs
  2. 0 122
      Emby.Server.Implementations/SyncPlay/GroupStates/IdleGroupState.cs
  3. 11 16
      Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
  4. 5 7
      Jellyfin.Api/Controllers/SyncPlayController.cs
  5. 64 66
      MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs
  6. 126 0
      MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs
  7. 37 33
      MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs
  8. 43 41
      MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs
  9. 107 135
      MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
  10. 4 5
      MediaBrowser.Controller/SyncPlay/IGroupController.cs
  11. 27 0
      MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs
  12. 26 26
      MediaBrowser.Controller/SyncPlay/IGroupState.cs
  13. 10 7
      MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs
  14. 0 23
      MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs
  15. 3 3
      MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs
  16. 0 30
      MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs
  17. 0 24
      MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs
  18. 0 30
      MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs
  19. 0 24
      MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs
  20. 0 24
      MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs
  21. 6 9
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs
  22. 27 0
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs
  23. 5 8
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs
  24. 5 8
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextTrackGroupRequest.cs
  25. 21 0
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs
  26. 5 8
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs
  27. 8 10
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs
  28. 5 8
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousTrackGroupRequest.cs
  29. 8 10
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs
  30. 6 9
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs
  31. 28 0
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs
  32. 5 8
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs
  33. 5 8
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs
  34. 5 8
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs
  35. 5 8
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs
  36. 21 0
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/StopGroupRequest.cs
  37. 21 0
      MediaBrowser.Controller/SyncPlay/PlaybackRequests/UnpauseGroupRequest.cs
  38. 180 135
      MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
  39. 1 1
      MediaBrowser.Model/SyncPlay/GroupInfoDto.cs
  40. 4 1
      MediaBrowser.Model/SyncPlay/GroupStateType.cs
  41. 1 3
      MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs
  42. 1 0
      MediaBrowser.Model/SyncPlay/GroupUpdate.cs
  43. 3 1
      MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs
  44. 1 0
      MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs

+ 125 - 126
Emby.Server.Implementations/SyncPlay/GroupController.cs

@@ -16,28 +16,13 @@ using Microsoft.Extensions.Logging;
 namespace Emby.Server.Implementations.SyncPlay
 namespace Emby.Server.Implementations.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
-    /// Class SyncPlayGroupController.
+    /// Class GroupController.
     /// </summary>
     /// </summary>
     /// <remarks>
     /// <remarks>
     /// Class is not thread-safe, external locking is required when accessing methods.
     /// Class is not thread-safe, external locking is required when accessing methods.
     /// </remarks>
     /// </remarks>
-    public class SyncPlayGroupController : ISyncPlayGroupController, ISyncPlayStateContext
+    public class GroupController : IGroupController, IGroupStateContext
     {
     {
-        /// <summary>
-        /// Gets the default ping value used for sessions.
-        /// </summary>
-        public long DefaultPing { get; } = 500;
-
-        /// <summary>
-        /// Gets the maximum time offset error accepted for dates reported by clients, in milliseconds.
-        /// </summary>
-        public long TimeSyncOffset { get; } = 2000;
-
-        /// <summary>
-        /// Gets the maximum offset error accepted for position reported by clients, in milliseconds.
-        /// </summary>
-        public long MaxPlaybackOffset { get; } = 500;
-
         /// <summary>
         /// <summary>
         /// The logger.
         /// The logger.
         /// </summary>
         /// </summary>
@@ -67,7 +52,46 @@ namespace Emby.Server.Implementations.SyncPlay
         /// Internal group state.
         /// Internal group state.
         /// </summary>
         /// </summary>
         /// <value>The group's state.</value>
         /// <value>The group's state.</value>
-        private ISyncPlayState State;
+        private IGroupState _state;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="GroupController" /> class.
+        /// </summary>
+        /// <param name="logger">The logger.</param>
+        /// <param name="userManager">The user manager.</param>
+        /// <param name="sessionManager">The session manager.</param>
+        /// <param name="libraryManager">The library manager.</param>
+        /// <param name="syncPlayManager">The SyncPlay manager.</param>
+        public GroupController(
+            ILogger logger,
+            IUserManager userManager,
+            ISessionManager sessionManager,
+            ILibraryManager libraryManager,
+            ISyncPlayManager syncPlayManager)
+        {
+            _logger = logger;
+            _userManager = userManager;
+            _sessionManager = sessionManager;
+            _libraryManager = libraryManager;
+            _syncPlayManager = syncPlayManager;
+
+            _state = new IdleGroupState(_logger);
+        }
+
+        /// <summary>
+        /// Gets the default ping value used for sessions.
+        /// </summary>
+        public long DefaultPing { get; } = 500;
+
+        /// <summary>
+        /// Gets the maximum time offset error accepted for dates reported by clients, in milliseconds.
+        /// </summary>
+        public long TimeSyncOffset { get; } = 2000;
+
+        /// <summary>
+        /// Gets the maximum offset error accepted for position reported by clients, in milliseconds.
+        /// </summary>
+        public long MaxPlaybackOffset { get; } = 500;
 
 
         /// <summary>
         /// <summary>
         /// Gets the group identifier.
         /// Gets the group identifier.
@@ -88,7 +112,7 @@ namespace Emby.Server.Implementations.SyncPlay
         public PlayQueueManager PlayQueue { get; } = new PlayQueueManager();
         public PlayQueueManager PlayQueue { get; } = new PlayQueueManager();
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the runtime ticks of current playing item.
+        /// Gets the runtime ticks of current playing item.
         /// </summary>
         /// </summary>
         /// <value>The runtime ticks of current playing item.</value>
         /// <value>The runtime ticks of current playing item.</value>
         public long RunTimeTicks { get; private set; }
         public long RunTimeTicks { get; private set; }
@@ -112,30 +136,6 @@ namespace Emby.Server.Implementations.SyncPlay
         public Dictionary<string, GroupMember> Participants { get; } =
         public Dictionary<string, GroupMember> Participants { get; } =
             new Dictionary<string, GroupMember>(StringComparer.OrdinalIgnoreCase);
             new Dictionary<string, GroupMember>(StringComparer.OrdinalIgnoreCase);
 
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="SyncPlayGroupController" /> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="userManager">The user manager.</param>
-        /// <param name="sessionManager">The session manager.</param>
-        /// <param name="libraryManager">The library manager.</param>
-        /// <param name="syncPlayManager">The SyncPlay manager.</param>
-        public SyncPlayGroupController(
-            ILogger logger,
-            IUserManager userManager,
-            ISessionManager sessionManager,
-            ILibraryManager libraryManager,
-            ISyncPlayManager syncPlayManager)
-        {
-            _logger = logger;
-            _userManager = userManager;
-            _sessionManager = sessionManager;
-            _libraryManager = libraryManager;
-            _syncPlayManager = syncPlayManager;
-
-            State = new IdleGroupState(_logger);
-        }
-
         /// <summary>
         /// <summary>
         /// Adds the session to the group.
         /// Adds the session to the group.
         /// </summary>
         /// </summary>
@@ -167,34 +167,32 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <param name="from">The current session.</param>
         /// <param name="from">The current session.</param>
         /// <param name="type">The filtering type.</param>
         /// <param name="type">The filtering type.</param>
         /// <returns>The array of sessions matching the filter.</returns>
         /// <returns>The array of sessions matching the filter.</returns>
-        private SessionInfo[] FilterSessions(SessionInfo from, SyncPlayBroadcastType type)
-        {
-            switch (type)
-            {
-                case SyncPlayBroadcastType.CurrentSession:
-                    return new SessionInfo[] { from };
-                case SyncPlayBroadcastType.AllGroup:
-                    return Participants
-                        .Values
-                        .Select(session => session.Session)
-                        .ToArray();
-                case SyncPlayBroadcastType.AllExceptCurrentSession:
-                    return Participants
-                        .Values
-                        .Select(session => session.Session)
-                        .Where(session => !session.Id.Equals(from.Id))
-                        .ToArray();
-                case SyncPlayBroadcastType.AllReady:
-                    return Participants
-                        .Values
-                        .Where(session => !session.IsBuffering)
-                        .Select(session => session.Session)
-                        .ToArray();
-                default:
-                    return Array.Empty<SessionInfo>();
-            }
+        private IEnumerable<SessionInfo> FilterSessions(SessionInfo from, SyncPlayBroadcastType type)
+        {
+            return type switch
+            {
+                SyncPlayBroadcastType.CurrentSession => new SessionInfo[] { from },
+                SyncPlayBroadcastType.AllGroup => Participants
+                    .Values
+                    .Select(session => session.Session),
+                SyncPlayBroadcastType.AllExceptCurrentSession => Participants
+                    .Values
+                    .Select(session => session.Session)
+                    .Where(session => !session.Id.Equals(from.Id, StringComparison.OrdinalIgnoreCase)),
+                SyncPlayBroadcastType.AllReady => Participants
+                    .Values
+                    .Where(session => !session.IsBuffering)
+                    .Select(session => session.Session),
+                _ => Enumerable.Empty<SessionInfo>()
+            };
         }
         }
 
 
+        /// <summary>
+        /// Checks if a given user can access a given item, that is, the user has access to a folder where the item is stored.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="item">The item.</param>
+        /// <returns><c>true</c> if the user can access the item, <c>false</c> otherwise.</returns>
         private bool HasAccessToItem(User user, BaseItem item)
         private bool HasAccessToItem(User user, BaseItem item)
         {
         {
             var collections = _libraryManager.GetCollectionFolders(item)
             var collections = _libraryManager.GetCollectionFolders(item)
@@ -202,41 +200,42 @@ namespace Emby.Server.Implementations.SyncPlay
             return collections.Intersect(user.GetPreference(PreferenceKind.EnabledFolders)).Any();
             return collections.Intersect(user.GetPreference(PreferenceKind.EnabledFolders)).Any();
         }
         }
 
 
-        private bool HasAccessToQueue(User user, Guid[] queue)
+        /// <summary>
+        /// Checks if a given user can access all items of a given queue, that is,
+        /// the user has the required minimum parental access and has access to all required folders.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <param name="queue">The queue.</param>
+        /// <returns><c>true</c> if the user can access all the items in the queue, <c>false</c> otherwise.</returns>
+        private bool HasAccessToQueue(User user, IEnumerable<Guid> queue)
         {
         {
-            if (queue == null || queue.Length == 0)
+            // Check if queue is empty.
+            if (!queue?.Any() ?? true)
             {
             {
                 return true;
                 return true;
             }
             }
 
 
-            var items = queue.ToList()
-                .Select(item => _libraryManager.GetItemById(item));
-
-            // Find the highest rating value, which becomes the required minimum for the user.
-            var MinParentalRatingAccessRequired = items
-                .Select(item => item.InheritedParentalRatingValue)
-                .Min();
-
-            // Check ParentalRating access, user must have the minimum required access level.
-            var hasParentalRatingAccess = !user.MaxParentalAgeRating.HasValue
-                || MinParentalRatingAccessRequired <= user.MaxParentalAgeRating;
-
-            // Check that user has access to all required folders.
-            if (!user.HasPermission(PermissionKind.EnableAllFolders) && hasParentalRatingAccess)
+            foreach (var itemId in queue)
             {
             {
-                // Get list of items that are not accessible.
-                var blockedItems = items.Where(item => !HasAccessToItem(user, item));
+                var item = _libraryManager.GetItemById(itemId);
+                if (user.MaxParentalAgeRating.HasValue && item.InheritedParentalRatingValue > user.MaxParentalAgeRating)
+                {
+                    return false;
+                }
 
 
-                // We need the user to be able to access all items.
-                return !blockedItems.Any();
+                if (!user.HasPermission(PermissionKind.EnableAllFolders) && !HasAccessToItem(user, item))
+                {
+                    return false;
+                }
             }
             }
 
 
-            return hasParentalRatingAccess;
+            return true;
         }
         }
 
 
-        private bool AllUsersHaveAccessToQueue(Guid[] queue)
+        private bool AllUsersHaveAccessToQueue(IEnumerable<Guid> queue)
         {
         {
-            if (queue == null || queue.Length == 0)
+            // Check if queue is empty.
+            if (!queue?.Any() ?? true)
             {
             {
                 return true;
                 return true;
             }
             }
@@ -269,7 +268,7 @@ namespace Emby.Server.Implementations.SyncPlay
 
 
             if (sessionIsPlayingAnItem)
             if (sessionIsPlayingAnItem)
             {
             {
-                var playlist = session.NowPlayingQueue.Select(item => item.Id).ToArray();
+                var playlist = session.NowPlayingQueue.Select(item => item.Id);
                 PlayQueue.Reset();
                 PlayQueue.Reset();
                 PlayQueue.SetPlaylist(playlist);
                 PlayQueue.SetPlaylist(playlist);
                 PlayQueue.SetPlayingItemById(session.FullNowPlayingItem.Id);
                 PlayQueue.SetPlayingItemById(session.FullNowPlayingItem.Id);
@@ -277,17 +276,19 @@ namespace Emby.Server.Implementations.SyncPlay
                 PositionTicks = session.PlayState.PositionTicks ?? 0;
                 PositionTicks = session.PlayState.PositionTicks ?? 0;
 
 
                 // Mantain playstate.
                 // Mantain playstate.
-                var waitingState = new WaitingGroupState(_logger);
-                waitingState.ResumePlaying = !session.PlayState.IsPaused;
+                var waitingState = new WaitingGroupState(_logger)
+                {
+                    ResumePlaying = !session.PlayState.IsPaused
+                };
                 SetState(waitingState);
                 SetState(waitingState);
             }
             }
 
 
             var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo());
             var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo());
             SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
             SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 
 
-            State.SessionJoined(this, State.GetGroupState(), session, cancellationToken);
+            _state.SessionJoined(this, _state.Type, session, cancellationToken);
 
 
-            _logger.LogInformation("InitGroup: {0} created group {1}.", session.Id.ToString(), GroupId.ToString());
+            _logger.LogInformation("InitGroup: {0} created group {1}.", session.Id, GroupId.ToString());
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -302,9 +303,9 @@ namespace Emby.Server.Implementations.SyncPlay
             var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
             var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
             SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
             SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 
 
-            State.SessionJoined(this, State.GetGroupState(), session, cancellationToken);
+            _state.SessionJoined(this, _state.Type, session, cancellationToken);
 
 
-            _logger.LogInformation("SessionJoin: {0} joined group {1}.", session.Id.ToString(), GroupId.ToString());
+            _logger.LogInformation("SessionJoin: {0} joined group {1}.", session.Id, GroupId.ToString());
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -316,15 +317,15 @@ namespace Emby.Server.Implementations.SyncPlay
             var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
             var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
             SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
             SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 
 
-            State.SessionJoined(this, State.GetGroupState(), session, cancellationToken);
+            _state.SessionJoined(this, _state.Type, session, cancellationToken);
 
 
-            _logger.LogInformation("SessionRestore: {0} re-joined group {1}.", session.Id.ToString(), GroupId.ToString());
+            _logger.LogInformation("SessionRestore: {0} re-joined group {1}.", session.Id, GroupId.ToString());
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
         public void SessionLeave(SessionInfo session, CancellationToken cancellationToken)
         public void SessionLeave(SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            State.SessionLeaving(this, State.GetGroupState(), session, cancellationToken);
+            _state.SessionLeaving(this, _state.Type, session, cancellationToken);
 
 
             RemoveSession(session);
             RemoveSession(session);
             _syncPlayManager.RemoveSessionFromGroup(session, this);
             _syncPlayManager.RemoveSessionFromGroup(session, this);
@@ -335,18 +336,17 @@ namespace Emby.Server.Implementations.SyncPlay
             var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName);
             var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName);
             SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
             SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 
 
-            _logger.LogInformation("SessionLeave: {0} left group {1}.", session.Id.ToString(), GroupId.ToString());
+            _logger.LogInformation("SessionLeave: {0} left group {1}.", session.Id, GroupId.ToString());
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken)
+        public void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken)
         {
         {
             // The server's job is to maintain a consistent state for clients to reference
             // The server's job is to maintain a consistent state for clients to reference
             // and notify clients of state changes. The actual syncing of media playback
             // and notify clients of state changes. The actual syncing of media playback
             // happens client side. Clients are aware of the server's time and use it to sync.
             // happens client side. Clients are aware of the server's time and use it to sync.
-            _logger.LogInformation("HandleRequest: {0} requested {1}, group {2} in {3} state.",
-                session.Id.ToString(), request.GetRequestType(), GroupId.ToString(), State.GetGroupState());
-            request.Apply(this, State, session, cancellationToken);
+            _logger.LogInformation("HandleRequest: {0} requested {1}, group {2} in {3} state.", session.Id, request.Type, GroupId.ToString(), _state.Type);
+            request.Apply(this, _state, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -356,7 +356,7 @@ namespace Emby.Server.Implementations.SyncPlay
             {
             {
                 GroupId = GroupId.ToString(),
                 GroupId = GroupId.ToString(),
                 GroupName = GroupName,
                 GroupName = GroupName,
-                State = State.GetGroupState(),
+                State = _state.Type,
                 Participants = Participants.Values.Select(session => session.Session.UserName).Distinct().ToList(),
                 Participants = Participants.Values.Select(session => session.Session.UserName).Distinct().ToList(),
                 LastUpdatedAt = DateToUTCString(DateTime.UtcNow)
                 LastUpdatedAt = DateToUTCString(DateTime.UtcNow)
             };
             };
@@ -365,7 +365,7 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <inheritdoc />
         /// <inheritdoc />
         public bool HasAccessToPlayQueue(User user)
         public bool HasAccessToPlayQueue(User user)
         {
         {
-            var items = PlayQueue.GetPlaylist().Select(item => item.ItemId).ToArray();
+            var items = PlayQueue.GetPlaylist().Select(item => item.ItemId);
             return HasAccessToQueue(user, items);
             return HasAccessToQueue(user, items);
         }
         }
 
 
@@ -381,10 +381,10 @@ namespace Emby.Server.Implementations.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void SetState(ISyncPlayState state)
+        public void SetState(IGroupState state)
         {
         {
-            _logger.LogInformation("SetState: {0} switching from {1} to {2}.", GroupId.ToString(), State.GetGroupState(), state.GetGroupState());
-            this.State = state;
+            _logger.LogInformation("SetState: {0} switching from {1} to {2}.", GroupId.ToString(), _state.Type, state.Type);
+            this._state = state;
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -443,16 +443,14 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <inheritdoc />
         /// <inheritdoc />
         public string DateToUTCString(DateTime dateTime)
         public string DateToUTCString(DateTime dateTime)
         {
         {
-            return dateTime.ToUniversalTime().ToString("o");
+            return dateTime.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
         public long SanitizePositionTicks(long? positionTicks)
         public long SanitizePositionTicks(long? positionTicks)
         {
         {
             var ticks = positionTicks ?? 0;
             var ticks = positionTicks ?? 0;
-            ticks = Math.Max(ticks, 0);
-            ticks = Math.Min(ticks, RunTimeTicks);
-            return ticks;
+            return Math.Clamp(ticks, 0, RunTimeTicks);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
@@ -509,10 +507,10 @@ namespace Emby.Server.Implementations.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public bool SetPlayQueue(Guid[] playQueue, int playingItemPosition, long startPositionTicks)
+        public bool SetPlayQueue(IEnumerable<Guid> playQueue, int playingItemPosition, long startPositionTicks)
         {
         {
             // Ignore on empty queue or invalid item position.
             // Ignore on empty queue or invalid item position.
-            if (playQueue.Length < 1 || playingItemPosition >= playQueue.Length || playingItemPosition < 0)
+            if (!playQueue.Any() || playingItemPosition >= playQueue.Count() || playingItemPosition < 0)
             {
             {
                 return false;
                 return false;
             }
             }
@@ -555,7 +553,7 @@ namespace Emby.Server.Implementations.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public bool RemoveFromPlayQueue(string[] playlistItemIds)
+        public bool RemoveFromPlayQueue(IEnumerable<string> playlistItemIds)
         {
         {
             var playingItemRemoved = PlayQueue.RemoveFromPlaylist(playlistItemIds);
             var playingItemRemoved = PlayQueue.RemoveFromPlaylist(playlistItemIds);
             if (playingItemRemoved)
             if (playingItemRemoved)
@@ -584,10 +582,10 @@ namespace Emby.Server.Implementations.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public bool AddToPlayQueue(Guid[] newItems, string mode)
+        public bool AddToPlayQueue(IEnumerable<Guid> newItems, string mode)
         {
         {
             // Ignore on empty list.
             // Ignore on empty list.
-            if (newItems.Length < 1)
+            if (!newItems.Any())
             {
             {
                 return false;
                 return false;
             }
             }
@@ -598,7 +596,7 @@ namespace Emby.Server.Implementations.SyncPlay
                 return false;
                 return false;
             }
             }
 
 
-            if (mode.Equals("next"))
+            if (mode.Equals("next", StringComparison.OrdinalIgnoreCase))
             {
             {
                 PlayQueue.QueueNext(newItems);
                 PlayQueue.QueueNext(newItems);
             }
             }
@@ -652,7 +650,8 @@ namespace Emby.Server.Implementations.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void SetRepeatMode(string mode) {
+        public void SetRepeatMode(string mode)
+        {
             switch (mode)
             switch (mode)
             {
             {
                 case "RepeatOne":
                 case "RepeatOne":
@@ -669,7 +668,8 @@ namespace Emby.Server.Implementations.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void SetShuffleMode(string mode) {
+        public void SetShuffleMode(string mode)
+        {
             switch (mode)
             switch (mode)
             {
             {
                 case "Shuffle":
                 case "Shuffle":
@@ -687,7 +687,7 @@ namespace Emby.Server.Implementations.SyncPlay
         {
         {
             var startPositionTicks = PositionTicks;
             var startPositionTicks = PositionTicks;
 
 
-            if (State.GetGroupState().Equals(GroupState.Playing))
+            if (_state.Type.Equals(GroupStateType.Playing))
             {
             {
                 var currentTime = DateTime.UtcNow;
                 var currentTime = DateTime.UtcNow;
                 var elapsedTime = currentTime - LastActivity;
                 var elapsedTime = currentTime - LastActivity;
@@ -711,6 +711,5 @@ namespace Emby.Server.Implementations.SyncPlay
                 RepeatMode = PlayQueue.RepeatMode
                 RepeatMode = PlayQueue.RepeatMode
             };
             };
         }
         }
-
     }
     }
 }
 }

+ 0 - 122
Emby.Server.Implementations/SyncPlay/GroupStates/IdleGroupState.cs

@@ -1,122 +0,0 @@
-using System.Threading;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.SyncPlay;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Controller.SyncPlay
-{
-    /// <summary>
-    /// Class IdleGroupState.
-    /// </summary>
-    /// <remarks>
-    /// Class is not thread-safe, external locking is required when accessing methods.
-    /// </remarks>
-    public class IdleGroupState : AbstractGroupState
-    {
-        /// <summary>
-        /// Default constructor.
-        /// </summary>
-        public IdleGroupState(ILogger logger)
-            : base(logger)
-        {
-            // Do nothing.
-        }
-
-        /// <inheritdoc />
-        public override GroupState GetGroupState()
-        {
-            return GroupState.Idle;
-        }
-
-        /// <inheritdoc />
-        public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
-        {
-            SendStopCommand(context, GetGroupState(), session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
-        {
-            // Do nothing.
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            // Change state.
-            var waitingState = new WaitingGroupState(_logger);
-            context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            // Change state.
-            var waitingState = new WaitingGroupState(_logger);
-            context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            SendStopCommand(context, prevState, session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            SendStopCommand(context, prevState, session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            SendStopCommand(context, prevState, session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            SendStopCommand(context, prevState, session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            SendStopCommand(context, prevState, session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            // Change state.
-            var waitingState = new WaitingGroupState(_logger);
-            context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
-        }
-
-        /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
-        {
-            // Change state.
-            var waitingState = new WaitingGroupState(_logger);
-            context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
-        }
-
-        private void SendStopCommand(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
-        {
-            var command = context.NewSyncPlayCommand(SendCommandType.Stop);
-            if (!prevState.Equals(GetGroupState()))
-            {
-                context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
-            }
-            else
-            {
-                context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
-            }
-        }
-    }
-}

+ 11 - 16
Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs

@@ -39,14 +39,14 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <summary>
         /// <summary>
         /// The map between sessions and groups.
         /// The map between sessions and groups.
         /// </summary>
         /// </summary>
-        private readonly Dictionary<string, ISyncPlayGroupController> _sessionToGroupMap =
-            new Dictionary<string, ISyncPlayGroupController>(StringComparer.OrdinalIgnoreCase);
+        private readonly Dictionary<string, IGroupController> _sessionToGroupMap =
+            new Dictionary<string, IGroupController>(StringComparer.OrdinalIgnoreCase);
 
 
         /// <summary>
         /// <summary>
         /// The groups.
         /// The groups.
         /// </summary>
         /// </summary>
-        private readonly Dictionary<Guid, ISyncPlayGroupController> _groups =
-            new Dictionary<Guid, ISyncPlayGroupController>();
+        private readonly Dictionary<Guid, IGroupController> _groups =
+            new Dictionary<Guid, IGroupController>();
 
 
         /// <summary>
         /// <summary>
         /// Lock used for accesing any group.
         /// Lock used for accesing any group.
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.SyncPlay
         /// Gets all groups.
         /// Gets all groups.
         /// </summary>
         /// </summary>
         /// <value>All groups.</value>
         /// <value>All groups.</value>
-        public IEnumerable<ISyncPlayGroupController> Groups => _groups.Values;
+        public IEnumerable<IGroupController> Groups => _groups.Values;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
         public void Dispose()
         public void Dispose()
@@ -229,7 +229,7 @@ namespace Emby.Server.Implementations.SyncPlay
                     LeaveGroup(session, cancellationToken);
                     LeaveGroup(session, cancellationToken);
                 }
                 }
 
 
-                var group = new SyncPlayGroupController(_logger, _userManager, _sessionManager, _libraryManager, this);
+                var group = new GroupController(_logger, _userManager, _sessionManager, _libraryManager, this);
                 _groups[group.GroupId] = group;
                 _groups[group.GroupId] = group;
 
 
                 group.CreateGroup(session, request, cancellationToken);
                 group.CreateGroup(session, request, cancellationToken);
@@ -249,7 +249,7 @@ namespace Emby.Server.Implementations.SyncPlay
 
 
             lock (_groupsLock)
             lock (_groupsLock)
             {
             {
-                _groups.TryGetValue(groupId, out ISyncPlayGroupController group);
+                _groups.TryGetValue(groupId, out IGroupController group);
 
 
                 if (group == null)
                 if (group == null)
                 {
                 {
@@ -346,7 +346,7 @@ namespace Emby.Server.Implementations.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken)
+        public void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken)
         {
         {
             // TODO: create abstract class for GroupRequests to avoid explicit request type here.
             // TODO: create abstract class for GroupRequests to avoid explicit request type here.
             if (!IsRequestValid(session, GroupRequestType.Playback, request))
             if (!IsRequestValid(session, GroupRequestType.Playback, request))
@@ -375,28 +375,23 @@ namespace Emby.Server.Implementations.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void AddSessionToGroup(SessionInfo session, ISyncPlayGroupController group)
+        public void AddSessionToGroup(SessionInfo session, IGroupController group)
         {
         {
             if (session == null)
             if (session == null)
             {
             {
                 throw new InvalidOperationException("Session is null!");
                 throw new InvalidOperationException("Session is null!");
             }
             }
 
 
-            if (group == null)
-            {
-                throw new InvalidOperationException("Group is null!");
-            }
-
             if (IsSessionInGroup(session))
             if (IsSessionInGroup(session))
             {
             {
                 throw new InvalidOperationException("Session in other group already!");
                 throw new InvalidOperationException("Session in other group already!");
             }
             }
 
 
-            _sessionToGroupMap[session.Id] = group;
+            _sessionToGroupMap[session.Id] = group ?? throw new InvalidOperationException("Group is null!");
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void RemoveSessionFromGroup(SessionInfo session, ISyncPlayGroupController group)
+        public void RemoveSessionFromGroup(SessionInfo session, IGroupController group)
         {
         {
             if (session == null)
             if (session == null)
             {
             {

+ 5 - 7
Jellyfin.Api/Controllers/SyncPlayController.cs

@@ -125,10 +125,10 @@ namespace Jellyfin.Api.Controllers
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var syncPlayRequest = new PlayGroupRequest()
             var syncPlayRequest = new PlayGroupRequest()
             {
             {
-                PlayingQueue = RequestHelpers.GetGuids(playingQueue),
                 PlayingItemPosition = playingItemPosition,
                 PlayingItemPosition = playingItemPosition,
                 StartPositionTicks = startPositionTicks
                 StartPositionTicks = startPositionTicks
             };
             };
+            syncPlayRequest.PlayingQueue.AddRange(RequestHelpers.GetGuids(playingQueue));
             _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
             _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
             return NoContent();
             return NoContent();
         }
         }
@@ -165,10 +165,8 @@ namespace Jellyfin.Api.Controllers
             [FromQuery, Required] string[] playlistItemIds)
             [FromQuery, Required] string[] playlistItemIds)
         {
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
-            var syncPlayRequest = new RemoveFromPlaylistGroupRequest()
-            {
-                PlaylistItemIds = playlistItemIds
-            };
+            var syncPlayRequest = new RemoveFromPlaylistGroupRequest();
+            syncPlayRequest.PlaylistItemIds.AddRange(playlistItemIds);
             _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
             _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
             return NoContent();
             return NoContent();
         }
         }
@@ -212,9 +210,9 @@ namespace Jellyfin.Api.Controllers
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var syncPlayRequest = new QueueGroupRequest()
             var syncPlayRequest = new QueueGroupRequest()
             {
             {
-                ItemIds = RequestHelpers.GetGuids(itemIds),
                 Mode = mode
                 Mode = mode
             };
             };
+            syncPlayRequest.ItemIds.AddRange(RequestHelpers.GetGuids(itemIds));
             _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
             _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
             return NoContent();
             return NoContent();
         }
         }
@@ -304,7 +302,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery, Required] bool bufferingDone)
             [FromQuery, Required] bool bufferingDone)
         {
         {
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
             var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request);
-            IPlaybackGroupRequest syncPlayRequest;
+            IGroupPlaybackRequest syncPlayRequest;
             if (!bufferingDone)
             if (!bufferingDone)
             {
             {
                 syncPlayRequest = new BufferGroupRequest()
                 syncPlayRequest = new BufferGroupRequest()

+ 64 - 66
Emby.Server.Implementations/SyncPlay/GroupStates/AbstractGroupState.cs → MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Threading;
 using System.Threading;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Model.SyncPlay;
@@ -11,71 +12,53 @@ namespace MediaBrowser.Controller.SyncPlay
     /// <remarks>
     /// <remarks>
     /// Class is not thread-safe, external locking is required when accessing methods.
     /// Class is not thread-safe, external locking is required when accessing methods.
     /// </remarks>
     /// </remarks>
-    public abstract class AbstractGroupState : ISyncPlayState
+    public abstract class AbstractGroupState : IGroupState
     {
     {
         /// <summary>
         /// <summary>
-        /// The logger.
+        /// Initializes a new instance of the <see cref="AbstractGroupState"/> class.
         /// </summary>
         /// </summary>
-        protected readonly ILogger _logger;
-
-        /// <summary>
-        /// Default constructor.
-        /// </summary>
-        public AbstractGroupState(ILogger logger)
+        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+        protected AbstractGroupState(ILogger logger)
         {
         {
-            _logger = logger;
+            Logger = logger;
         }
         }
 
 
+        /// <inheritdoc />
+        public abstract GroupStateType Type { get; }
+
         /// <summary>
         /// <summary>
-        /// Sends a group state update to all group.
+        /// Gets the logger.
         /// </summary>
         /// </summary>
-        /// <param name="context">The context of the state.</param>
-        /// <param name="reason">The reason of the state change.</param>
-        /// <param name="session">The session.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        protected void SendGroupStateUpdate(ISyncPlayStateContext context, IPlaybackGroupRequest reason, SessionInfo session, CancellationToken cancellationToken)
-        {
-            // Notify relevant state change event.
-            var stateUpdate = new GroupStateUpdate()
-            {
-                State = GetGroupState(),
-                Reason = reason.GetRequestType()
-            };
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.StateUpdate, stateUpdate);
-            context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
-        }
+        protected ILogger Logger { get; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public abstract GroupState GetGroupState();
+        public abstract void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public abstract void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken);
+        public abstract void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public abstract void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken);
-
-        /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, IGroupPlaybackRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, RemoveFromPlaylistGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, RemoveFromPlaylistGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             var playingItemRemoved = context.RemoveFromPlayQueue(request.PlaylistItemIds);
             var playingItemRemoved = context.RemoveFromPlayQueue(request.PlaylistItemIds);
 
 
@@ -83,29 +66,25 @@ namespace MediaBrowser.Controller.SyncPlay
             var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
             var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 
 
-            if (playingItemRemoved)
+            if (playingItemRemoved && !context.PlayQueue.IsItemPlaying())
             {
             {
-                var PlayingItemIndex = context.PlayQueue.PlayingItemIndex;
-                if (context.PlayQueue.PlayingItemIndex == -1)
-                {
-                    _logger.LogDebug("HandleRequest: {0} in group {1}, play queue is empty.", request.GetRequestType(), context.GroupId.ToString());
-
-                    ISyncPlayState idleState = new IdleGroupState(_logger);
-                    context.SetState(idleState);
-                    var stopRequest = new StopGroupRequest();
-                    idleState.HandleRequest(context, GetGroupState(), stopRequest, session, cancellationToken);
-                }
+                Logger.LogDebug("HandleRequest: {0} in group {1}, play queue is empty.", request.Type, context.GroupId.ToString());
+
+                IGroupState idleState = new IdleGroupState(Logger);
+                context.SetState(idleState);
+                var stopRequest = new StopGroupRequest();
+                idleState.HandleRequest(context, Type, stopRequest, session, cancellationToken);
             }
             }
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, MovePlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, MovePlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             var result = context.MoveItemInPlayQueue(request.PlaylistItemId, request.NewIndex);
             var result = context.MoveItemInPlayQueue(request.PlaylistItemId, request.NewIndex);
 
 
             if (!result)
             if (!result)
             {
             {
-                _logger.LogError("HandleRequest: {0} in group {1}, unable to move item in play queue.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogError("HandleRequest: {0} in group {1}, unable to move item in play queue.", request.Type, context.GroupId.ToString());
                 return;
                 return;
             }
             }
 
 
@@ -115,72 +94,72 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, QueueGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, QueueGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             var result = context.AddToPlayQueue(request.ItemIds, request.Mode);
             var result = context.AddToPlayQueue(request.ItemIds, request.Mode);
 
 
             if (!result)
             if (!result)
             {
             {
-                _logger.LogError("HandleRequest: {0} in group {1}, unable to add items to play queue.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogError("HandleRequest: {0} in group {1}, unable to add items to play queue.", request.Type, context.GroupId.ToString());
                 return;
                 return;
             }
             }
 
 
-            var reason = request.Mode.Equals("next") ? PlayQueueUpdateReason.QueueNext : PlayQueueUpdateReason.Queue;
+            var reason = request.Mode.Equals("next", StringComparison.OrdinalIgnoreCase) ? PlayQueueUpdateReason.QueueNext : PlayQueueUpdateReason.Queue;
             var playQueueUpdate = context.GetPlayQueueUpdate(reason);
             var playQueueUpdate = context.GetPlayQueueUpdate(reason);
             var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
             var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             UnhandledRequest(request);
             UnhandledRequest(request);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetRepeatModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetRepeatModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             context.SetRepeatMode(request.Mode);
             context.SetRepeatMode(request.Mode);
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RepeatMode);
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RepeatMode);
@@ -189,7 +168,7 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetShuffleModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetShuffleModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             context.SetShuffleMode(request.Mode);
             context.SetShuffleMode(request.Mode);
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.ShuffleMode);
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.ShuffleMode);
@@ -198,21 +177,40 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Collected pings are used to account for network latency when unpausing playback.
             // Collected pings are used to account for network latency when unpausing playback.
             context.UpdatePing(session, request.Ping);
             context.UpdatePing(session, request.Ping);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             context.SetIgnoreGroupWait(session, request.IgnoreWait);
             context.SetIgnoreGroupWait(session, request.IgnoreWait);
         }
         }
 
 
-        private void UnhandledRequest(IPlaybackGroupRequest request)
+        /// <summary>
+        /// Sends a group state update to all group.
+        /// </summary>
+        /// <param name="context">The context of the state.</param>
+        /// <param name="reason">The reason of the state change.</param>
+        /// <param name="session">The session.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        protected void SendGroupStateUpdate(IGroupStateContext context, IGroupPlaybackRequest reason, SessionInfo session, CancellationToken cancellationToken)
+        {
+            // Notify relevant state change event.
+            var stateUpdate = new GroupStateUpdate()
+            {
+                State = Type,
+                Reason = reason.Type
+            };
+            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.StateUpdate, stateUpdate);
+            context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+        }
+
+        private void UnhandledRequest(IGroupPlaybackRequest request)
         {
         {
-            _logger.LogWarning("HandleRequest: unhandled {0} request for {1} state.", request.GetRequestType(), GetGroupState());
+            Logger.LogWarning("HandleRequest: unhandled {0} request for {1} state.", request.Type, Type);
         }
         }
     }
     }
 }
 }

+ 126 - 0
MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs

@@ -0,0 +1,126 @@
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+    /// <summary>
+    /// Class IdleGroupState.
+    /// </summary>
+    /// <remarks>
+    /// Class is not thread-safe, external locking is required when accessing methods.
+    /// </remarks>
+    public class IdleGroupState : AbstractGroupState
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IdleGroupState"/> class.
+        /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+        public IdleGroupState(ILogger logger)
+            : base(logger)
+        {
+            // Do nothing.
+        }
+
+        /// <inheritdoc />
+        public override GroupStateType Type
+        {
+            get
+            {
+                return GroupStateType.Idle;
+            }
+        }
+
+        /// <inheritdoc />
+        public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
+        {
+            SendStopCommand(context, Type, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
+        {
+            // Do nothing.
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            // Change state.
+            var waitingState = new WaitingGroupState(Logger);
+            context.SetState(waitingState);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            // Change state.
+            var waitingState = new WaitingGroupState(Logger);
+            context.SetState(waitingState);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            SendStopCommand(context, prevState, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            SendStopCommand(context, prevState, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            SendStopCommand(context, prevState, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            SendStopCommand(context, prevState, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            SendStopCommand(context, prevState, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            // Change state.
+            var waitingState = new WaitingGroupState(Logger);
+            context.SetState(waitingState);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
+        }
+
+        /// <inheritdoc />
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        {
+            // Change state.
+            var waitingState = new WaitingGroupState(Logger);
+            context.SetState(waitingState);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
+        }
+
+        private void SendStopCommand(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
+        {
+            var command = context.NewSyncPlayCommand(SendCommandType.Stop);
+            if (!prevState.Equals(Type))
+            {
+                context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
+            }
+            else
+            {
+                context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+            }
+        }
+    }
+}

+ 37 - 33
Emby.Server.Implementations/SyncPlay/GroupStates/PausedGroupState.cs → MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs

@@ -15,8 +15,9 @@ namespace MediaBrowser.Controller.SyncPlay
     public class PausedGroupState : AbstractGroupState
     public class PausedGroupState : AbstractGroupState
     {
     {
         /// <summary>
         /// <summary>
-        /// Default constructor.
+        /// Initializes a new instance of the <see cref="PausedGroupState"/> class.
         /// </summary>
         /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
         public PausedGroupState(ILogger logger)
         public PausedGroupState(ILogger logger)
             : base(logger)
             : base(logger)
         {
         {
@@ -24,48 +25,51 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override GroupState GetGroupState()
+        public override GroupStateType Type
         {
         {
-            return GroupState.Paused;
+            get
+            {
+                return GroupStateType.Paused;
+            }
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
+        public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Wait for session to be ready.
             // Wait for session to be ready.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.SessionJoined(context, GetGroupState(), session, cancellationToken);
+            waitingState.SessionJoined(context, Type, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
+        public override void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Do nothing.
             // Do nothing.
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var playingState = new PlayingGroupState(_logger);
+            var playingState = new PlayingGroupState(Logger);
             context.SetState(playingState);
             context.SetState(playingState);
-            playingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            playingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            if (!prevState.Equals(GetGroupState()))
+            if (!prevState.Equals(Type))
             {
             {
                 // Pause group and compute the media playback position.
                 // Pause group and compute the media playback position.
                 var currentTime = DateTime.UtcNow;
                 var currentTime = DateTime.UtcNow;
@@ -94,42 +98,42 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var idleState = new IdleGroupState(_logger);
+            var idleState = new IdleGroupState(Logger);
             context.SetState(idleState);
             context.SetState(idleState);
-            idleState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            idleState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            if (prevState.Equals(GetGroupState()))
+            if (prevState.Equals(Type))
             {
             {
                 // Client got lost, sending current state.
                 // Client got lost, sending current state.
                 var command = context.NewSyncPlayCommand(SendCommandType.Pause);
                 var command = context.NewSyncPlayCommand(SendCommandType.Pause);
                 context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
                 context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
             }
             }
-            else if (prevState.Equals(GroupState.Waiting))
+            else if (prevState.Equals(GroupStateType.Waiting))
             {
             {
                 // Sending current state to all clients.
                 // Sending current state to all clients.
                 var command = context.NewSyncPlayCommand(SendCommandType.Pause);
                 var command = context.NewSyncPlayCommand(SendCommandType.Pause);
@@ -141,21 +145,21 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 43 - 41
Emby.Server.Implementations/SyncPlay/GroupStates/PlayingGroupState.cs → MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs

@@ -15,13 +15,9 @@ namespace MediaBrowser.Controller.SyncPlay
     public class PlayingGroupState : AbstractGroupState
     public class PlayingGroupState : AbstractGroupState
     {
     {
         /// <summary>
         /// <summary>
-        /// Ignore requests for buffering.
-        /// </summary>
-        public bool IgnoreBuffering { get; set; }
-
-        /// <summary>
-        /// Default constructor.
+        /// Initializes a new instance of the <see cref="PlayingGroupState"/> class.
         /// </summary>
         /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
         public PlayingGroupState(ILogger logger)
         public PlayingGroupState(ILogger logger)
             : base(logger)
             : base(logger)
         {
         {
@@ -29,39 +25,47 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override GroupState GetGroupState()
+        public override GroupStateType Type
         {
         {
-            return GroupState.Playing;
+            get
+            {
+                return GroupStateType.Playing;
+            }
         }
         }
 
 
+        /// <summary>
+        /// Gets or sets a value indicating whether requests for buffering should be ignored.
+        /// </summary>
+        public bool IgnoreBuffering { get; set; }
+
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
+        public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Wait for session to be ready.
             // Wait for session to be ready.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.SessionJoined(context, GetGroupState(), session, cancellationToken);
+            waitingState.SessionJoined(context, Type, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
+        public override void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Do nothing.
             // Do nothing.
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            if (!prevState.Equals(GetGroupState()))
+            if (!prevState.Equals(Type))
             {
             {
                 // Pick a suitable time that accounts for latency.
                 // Pick a suitable time that accounts for latency.
                 var delayMillis = Math.Max(context.GetHighestPing() * 2, context.DefaultPing);
                 var delayMillis = Math.Max(context.GetHighestPing() * 2, context.DefaultPing);
@@ -70,9 +74,7 @@ namespace MediaBrowser.Controller.SyncPlay
                 // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position).
                 // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position).
                 // The added delay does not guarantee, of course, that the command will be received in time.
                 // The added delay does not guarantee, of course, that the command will be received in time.
                 // Playback synchronization will mainly happen client side.
                 // Playback synchronization will mainly happen client side.
-                context.LastActivity = DateTime.UtcNow.AddMilliseconds(
-                    delayMillis
-                );
+                context.LastActivity = DateTime.UtcNow.AddMilliseconds(delayMillis);
 
 
                 var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
                 var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
                 context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
                 context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
@@ -89,34 +91,34 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var pausedState = new PausedGroupState(_logger);
+            var pausedState = new PausedGroupState(Logger);
             context.SetState(pausedState);
             context.SetState(pausedState);
-            pausedState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            pausedState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var idleState = new IdleGroupState(_logger);
+            var idleState = new IdleGroupState(Logger);
             context.SetState(idleState);
             context.SetState(idleState);
-            idleState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            idleState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             if (IgnoreBuffering)
             if (IgnoreBuffering)
             {
             {
@@ -124,21 +126,21 @@ namespace MediaBrowser.Controller.SyncPlay
             }
             }
 
 
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            if (prevState.Equals(GetGroupState()))
+            if (prevState.Equals(Type))
             {
             {
                 // Group was not waiting, make sure client has latest state.
                 // Group was not waiting, make sure client has latest state.
                 var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
                 var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
                 context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
                 context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
             }
             }
-            else if (prevState.Equals(GroupState.Waiting))
+            else if (prevState.Equals(GroupStateType.Waiting))
             {
             {
                 // Notify relevant state change event.
                 // Notify relevant state change event.
                 SendGroupStateUpdate(context, request, session, cancellationToken);
                 SendGroupStateUpdate(context, request, session, cancellationToken);
@@ -146,21 +148,21 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Change state.
             // Change state.
-            var waitingState = new WaitingGroupState(_logger);
+            var waitingState = new WaitingGroupState(Logger);
             context.SetState(waitingState);
             context.SetState(waitingState);
-            waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            waitingState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 107 - 135
Emby.Server.Implementations/SyncPlay/GroupStates/WaitingGroupState.cs → MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs

@@ -15,23 +15,9 @@ namespace MediaBrowser.Controller.SyncPlay
     public class WaitingGroupState : AbstractGroupState
     public class WaitingGroupState : AbstractGroupState
     {
     {
         /// <summary>
         /// <summary>
-        /// Tells the state to switch to after buffering is done.
-        /// </summary>
-        public bool ResumePlaying { get; set; } = false;
-
-        /// <summary>
-        /// Whether the initial state has been set.
-        /// </summary>
-        private bool InitialStateSet { get; set; } = false;
-
-        /// <summary>
-        /// The group state before the first ever event.
-        /// </summary>
-        private GroupState InitialState { get; set; }
-
-        /// <summary>
-        /// Default constructor.
+        /// Initializes a new instance of the <see cref="WaitingGroupState"/> class.
         /// </summary>
         /// </summary>
+        /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
         public WaitingGroupState(ILogger logger)
         public WaitingGroupState(ILogger logger)
             : base(logger)
             : base(logger)
         {
         {
@@ -39,13 +25,31 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override GroupState GetGroupState()
+        public override GroupStateType Type
         {
         {
-            return GroupState.Waiting;
+            get
+            {
+                return GroupStateType.Waiting;
+            }
         }
         }
 
 
+        /// <summary>
+        /// Gets or sets a value indicating whether playback should resume when group is ready.
+        /// </summary>
+        public bool ResumePlaying { get; set; } = false;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the initial state has been set.
+        /// </summary>
+        private bool InitialStateSet { get; set; } = false;
+
+        /// <summary>
+        /// Gets or sets the group state before the first ever event.
+        /// </summary>
+        private GroupStateType InitialState { get; set; }
+
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
+        public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -54,7 +58,8 @@ namespace MediaBrowser.Controller.SyncPlay
                 InitialStateSet = true;
                 InitialStateSet = true;
             }
             }
 
 
-            if (prevState.Equals(GroupState.Playing)) {
+            if (prevState.Equals(GroupStateType.Playing))
+            {
                 ResumePlaying = true;
                 ResumePlaying = true;
                 // Pause group and compute the media playback position.
                 // Pause group and compute the media playback position.
                 var currentTime = DateTime.UtcNow;
                 var currentTime = DateTime.UtcNow;
@@ -82,7 +87,7 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
+        public override void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -98,26 +103,26 @@ namespace MediaBrowser.Controller.SyncPlay
                 if (ResumePlaying)
                 if (ResumePlaying)
                 {
                 {
                     // Client, that was buffering, left the group.
                     // Client, that was buffering, left the group.
-                    var playingState = new PlayingGroupState(_logger);
+                    var playingState = new PlayingGroupState(Logger);
                     context.SetState(playingState);
                     context.SetState(playingState);
                     var unpauseRequest = new UnpauseGroupRequest();
                     var unpauseRequest = new UnpauseGroupRequest();
-                    playingState.HandleRequest(context, GetGroupState(), unpauseRequest, session, cancellationToken);
+                    playingState.HandleRequest(context, Type, unpauseRequest, session, cancellationToken);
 
 
-                    _logger.LogDebug("SessionLeaving: {0} left the group {1}, notifying others to resume.", session.Id.ToString(), context.GroupId.ToString());
+                    Logger.LogDebug("SessionLeaving: {0} left the group {1}, notifying others to resume.", session.Id, context.GroupId.ToString());
                 }
                 }
                 else
                 else
                 {
                 {
                     // Group is ready, returning to previous state.
                     // Group is ready, returning to previous state.
-                    var pausedState = new PausedGroupState(_logger);
+                    var pausedState = new PausedGroupState(Logger);
                     context.SetState(pausedState);
                     context.SetState(pausedState);
 
 
-                    _logger.LogDebug("SessionLeaving: {0} left the group {1}, returning to previous state.", session.Id.ToString(), context.GroupId.ToString());
+                    Logger.LogDebug("SessionLeaving: {0} left the group {1}, returning to previous state.", session.Id, context.GroupId.ToString());
                 }
                 }
             }
             }
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -131,22 +136,14 @@ namespace MediaBrowser.Controller.SyncPlay
             var setQueueStatus = context.SetPlayQueue(request.PlayingQueue, request.PlayingItemPosition, request.StartPositionTicks);
             var setQueueStatus = context.SetPlayQueue(request.PlayingQueue, request.PlayingItemPosition, request.StartPositionTicks);
             if (!setQueueStatus)
             if (!setQueueStatus)
             {
             {
-                _logger.LogError("HandleRequest: {0} in group {1}, unable to set playing queue.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogError("HandleRequest: {0} in group {1}, unable to set playing queue.", request.Type, context.GroupId.ToString());
 
 
                 // Ignore request and return to previous state.
                 // Ignore request and return to previous state.
-                ISyncPlayState newState;
-                switch (prevState)
-                {
-                    case GroupState.Playing:
-                        newState = new PlayingGroupState(_logger);
-                        break;
-                    case GroupState.Paused:
-                        newState = new PausedGroupState(_logger);
-                        break;
-                    default:
-                        newState = new IdleGroupState(_logger);
-                        break;
-                }
+                IGroupState newState = prevState switch {
+                    GroupStateType.Playing => new PlayingGroupState(Logger),
+                    GroupStateType.Paused => new PausedGroupState(Logger),
+                    _ => new IdleGroupState(Logger)
+                };
 
 
                 context.SetState(newState);
                 context.SetState(newState);
                 return;
                 return;
@@ -159,11 +156,11 @@ namespace MediaBrowser.Controller.SyncPlay
             // Reset status of sessions and await for all Ready events.
             // Reset status of sessions and await for all Ready events.
             context.SetAllBuffering(true);
             context.SetAllBuffering(true);
 
 
-            _logger.LogDebug("HandleRequest: {0} in group {1}, {2} set a new play queue.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
+            Logger.LogDebug("HandleRequest: {0} in group {1}, {2} set a new play queue.", request.Type, context.GroupId.ToString(), session.Id);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -187,28 +184,21 @@ namespace MediaBrowser.Controller.SyncPlay
             else
             else
             {
             {
                 // Return to old state.
                 // Return to old state.
-                ISyncPlayState newState;
-                switch (prevState)
+                IGroupState newState = prevState switch
                 {
                 {
-                    case GroupState.Playing:
-                        newState = new PlayingGroupState(_logger);
-                        break;
-                    case GroupState.Paused:
-                        newState = new PausedGroupState(_logger);
-                        break;
-                    default:
-                        newState = new IdleGroupState(_logger);
-                        break;
-                }
+                    GroupStateType.Playing => new PlayingGroupState(Logger),
+                    GroupStateType.Paused => new PausedGroupState(Logger),
+                    _ => new IdleGroupState(Logger)
+                };
 
 
                 context.SetState(newState);
                 context.SetState(newState);
 
 
-                _logger.LogDebug("HandleRequest: {0} in group {1}, unable to change current playing item.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogDebug("HandleRequest: {0} in group {1}, unable to change current playing item.", request.Type, context.GroupId.ToString());
             }
             }
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -217,7 +207,7 @@ namespace MediaBrowser.Controller.SyncPlay
                 InitialStateSet = true;
                 InitialStateSet = true;
             }
             }
 
 
-            if (prevState.Equals(GroupState.Idle))
+            if (prevState.Equals(GroupStateType.Idle))
             {
             {
                 ResumePlaying = true;
                 ResumePlaying = true;
                 context.RestartCurrentItem();
                 context.RestartCurrentItem();
@@ -229,22 +219,24 @@ namespace MediaBrowser.Controller.SyncPlay
                 // Reset status of sessions and await for all Ready events.
                 // Reset status of sessions and await for all Ready events.
                 context.SetAllBuffering(true);
                 context.SetAllBuffering(true);
 
 
-                _logger.LogDebug("HandleRequest: {0} in group {1}, waiting for all ready events.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogDebug("HandleRequest: {0} in group {1}, waiting for all ready events.", request.Type, context.GroupId.ToString());
             }
             }
             else
             else
             {
             {
                 if (ResumePlaying)
                 if (ResumePlaying)
                 {
                 {
-                    _logger.LogDebug("HandleRequest: {0} in group {1}, ignoring sessions that are not ready and forcing the playback to start.", request.GetRequestType(), context.GroupId.ToString());
+                    Logger.LogDebug("HandleRequest: {0} in group {1}, ignoring sessions that are not ready and forcing the playback to start.", request.Type, context.GroupId.ToString());
 
 
                     // An Unpause request is forcing the playback to start, ignoring sessions that are not ready.
                     // An Unpause request is forcing the playback to start, ignoring sessions that are not ready.
                     context.SetAllBuffering(false);
                     context.SetAllBuffering(false);
 
 
                     // Change state.
                     // Change state.
-                    var playingState = new PlayingGroupState(_logger);
-                    playingState.IgnoreBuffering = true;
+                    var playingState = new PlayingGroupState(Logger)
+                    {
+                        IgnoreBuffering = true
+                    };
                     context.SetState(playingState);
                     context.SetState(playingState);
-                    playingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+                    playingState.HandleRequest(context, Type, request, session, cancellationToken);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -258,7 +250,7 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -275,7 +267,7 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -285,13 +277,13 @@ namespace MediaBrowser.Controller.SyncPlay
             }
             }
 
 
             // Change state.
             // Change state.
-            var idleState = new IdleGroupState(_logger);
+            var idleState = new IdleGroupState(Logger);
             context.SetState(idleState);
             context.SetState(idleState);
-            idleState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+            idleState.HandleRequest(context, Type, request, session, cancellationToken);
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -300,11 +292,11 @@ namespace MediaBrowser.Controller.SyncPlay
                 InitialStateSet = true;
                 InitialStateSet = true;
             }
             }
 
 
-            if (prevState.Equals(GroupState.Playing))
+            if (prevState.Equals(GroupStateType.Playing))
             {
             {
                 ResumePlaying = true;
                 ResumePlaying = true;
             }
             }
-            else if(prevState.Equals(GroupState.Paused))
+            else if (prevState.Equals(GroupStateType.Paused))
             {
             {
                 ResumePlaying = false;
                 ResumePlaying = false;
             }
             }
@@ -327,7 +319,7 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -337,9 +329,9 @@ namespace MediaBrowser.Controller.SyncPlay
             }
             }
 
 
             // Make sure the client is playing the correct item.
             // Make sure the client is playing the correct item.
-            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
+            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase))
             {
             {
-                _logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
+                Logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.Type, context.GroupId.ToString(), session.Id);
 
 
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
                 var updateSession = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
                 var updateSession = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
@@ -349,7 +341,7 @@ namespace MediaBrowser.Controller.SyncPlay
                 return;
                 return;
             }
             }
 
 
-            if (prevState.Equals(GroupState.Playing))
+            if (prevState.Equals(GroupStateType.Playing))
             {
             {
                 // Resume playback when all ready.
                 // Resume playback when all ready.
                 ResumePlaying = true;
                 ResumePlaying = true;
@@ -372,7 +364,7 @@ namespace MediaBrowser.Controller.SyncPlay
                 var command = context.NewSyncPlayCommand(SendCommandType.Pause);
                 var command = context.NewSyncPlayCommand(SendCommandType.Pause);
                 context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
                 context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
             }
             }
-            else if (prevState.Equals(GroupState.Paused))
+            else if (prevState.Equals(GroupStateType.Paused))
             {
             {
                 // Don't resume playback when all ready.
                 // Don't resume playback when all ready.
                 ResumePlaying = false;
                 ResumePlaying = false;
@@ -383,7 +375,7 @@ namespace MediaBrowser.Controller.SyncPlay
                 var command = context.NewSyncPlayCommand(SendCommandType.Pause);
                 var command = context.NewSyncPlayCommand(SendCommandType.Pause);
                 context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
                 context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
             }
             }
-            else if (prevState.Equals(GroupState.Waiting))
+            else if (prevState.Equals(GroupStateType.Waiting))
             {
             {
                 // Another session is now buffering.
                 // Another session is now buffering.
                 context.SetBuffering(session, true);
                 context.SetBuffering(session, true);
@@ -401,7 +393,7 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -411,9 +403,9 @@ namespace MediaBrowser.Controller.SyncPlay
             }
             }
 
 
             // Make sure the client is playing the correct item.
             // Make sure the client is playing the correct item.
-            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
+            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase))
             {
             {
-                _logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
+                Logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.Type, context.GroupId.ToString(), session.Id);
 
 
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
                 var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
                 var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
@@ -433,8 +425,7 @@ namespace MediaBrowser.Controller.SyncPlay
             var timeSyncThresholdTicks = TimeSpan.FromMilliseconds(context.TimeSyncOffset).Ticks;
             var timeSyncThresholdTicks = TimeSpan.FromMilliseconds(context.TimeSyncOffset).Ticks;
             if (Math.Abs(elapsedTime.Ticks) > timeSyncThresholdTicks)
             if (Math.Abs(elapsedTime.Ticks) > timeSyncThresholdTicks)
             {
             {
-                _logger.LogWarning("HandleRequest: {0} in group {1}, {2} is not time syncing properly. Ignoring elapsed time.",
-                    request.GetRequestType(), context.GroupId.ToString(), session.Id);
+                Logger.LogWarning("HandleRequest: {0} in group {1}, {2} is not time syncing properly. Ignoring elapsed time.", request.Type, context.GroupId.ToString(), session.Id);
 
 
                 elapsedTime = TimeSpan.Zero;
                 elapsedTime = TimeSpan.Zero;
             }
             }
@@ -450,8 +441,7 @@ namespace MediaBrowser.Controller.SyncPlay
             var delayTicks = context.PositionTicks - clientPosition.Ticks;
             var delayTicks = context.PositionTicks - clientPosition.Ticks;
             var maxPlaybackOffsetTicks = TimeSpan.FromMilliseconds(context.MaxPlaybackOffset).Ticks;
             var maxPlaybackOffsetTicks = TimeSpan.FromMilliseconds(context.MaxPlaybackOffset).Ticks;
 
 
-            _logger.LogDebug("HandleRequest: {0} in group {1}, {2} at {3} (delay of {4} seconds).",
-                request.GetRequestType(), context.GroupId.ToString(), session.Id, clientPosition, TimeSpan.FromTicks(delayTicks).TotalSeconds);
+            Logger.LogDebug("HandleRequest: {0} in group {1}, {2} at {3} (delay of {4} seconds).", request.Type, context.GroupId.ToString(), session.Id, clientPosition, TimeSpan.FromTicks(delayTicks).TotalSeconds);
 
 
             if (ResumePlaying)
             if (ResumePlaying)
             {
             {
@@ -469,8 +459,7 @@ namespace MediaBrowser.Controller.SyncPlay
                     // Notify relevant state change event.
                     // Notify relevant state change event.
                     SendGroupStateUpdate(context, request, session, cancellationToken);
                     SendGroupStateUpdate(context, request, session, cancellationToken);
 
 
-                    _logger.LogWarning("HandleRequest: {0} in group {1}, {2} got lost in time, correcting.",
-                        request.GetRequestType(), context.GroupId.ToString(), session.Id);
+                    Logger.LogWarning("HandleRequest: {0} in group {1}, {2} got lost in time, correcting.", request.Type, context.GroupId.ToString(), session.Id);
                     return;
                     return;
                 }
                 }
 
 
@@ -485,8 +474,7 @@ namespace MediaBrowser.Controller.SyncPlay
                     command.When = context.DateToUTCString(pauseAtTime);
                     command.When = context.DateToUTCString(pauseAtTime);
                     context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
                     context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 
 
-                    _logger.LogInformation("HandleRequest: {0} in group {1}, others still buffering, {2} will pause when ready in {3} seconds.",
-                        request.GetRequestType(), context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds);
+                    Logger.LogInformation("HandleRequest: {0} in group {1}, others still buffering, {2} will pause when ready in {3} seconds.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -505,8 +493,7 @@ namespace MediaBrowser.Controller.SyncPlay
 
 
                         context.SendCommand(session, filter, command, cancellationToken);
                         context.SendCommand(session, filter, command, cancellationToken);
 
 
-                        _logger.LogInformation("HandleRequest: {0} in group {1}, {2} is recovering, notifying others to resume in {3} seconds.",
-                            request.GetRequestType(), context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds);
+                        Logger.LogInformation("HandleRequest: {0} in group {1}, {2} is recovering, notifying others to resume in {3} seconds.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds);
                     }
                     }
                     else
                     else
                     {
                     {
@@ -519,14 +506,13 @@ namespace MediaBrowser.Controller.SyncPlay
                         var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
                         var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
                         context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
                         context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
 
 
-                        _logger.LogWarning("HandleRequest: {0} in group {1}, {2} resumed playback but did not update others in time. {3} seconds to recover.",
-                            request.GetRequestType(), context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds);
+                        Logger.LogWarning("HandleRequest: {0} in group {1}, {2} resumed playback but did not update others in time. {3} seconds to recover.", request.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds);
                     }
                     }
 
 
                     // Change state.
                     // Change state.
-                    var playingState = new PlayingGroupState(_logger);
+                    var playingState = new PlayingGroupState(Logger);
                     context.SetState(playingState);
                     context.SetState(playingState);
-                    playingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+                    playingState.HandleRequest(context, Type, request, session, cancellationToken);
                 }
                 }
             }
             }
             else
             else
@@ -543,10 +529,11 @@ namespace MediaBrowser.Controller.SyncPlay
                     // Notify relevant state change event.
                     // Notify relevant state change event.
                     SendGroupStateUpdate(context, request, session, cancellationToken);
                     SendGroupStateUpdate(context, request, session, cancellationToken);
 
 
-                    _logger.LogWarning("HandleRequest: {0} in group {1}, {2} was seeking to wrong position, correcting.",
-                        request.GetRequestType(), context.GroupId.ToString(), session.Id);
+                    Logger.LogWarning("HandleRequest: {0} in group {1}, {2} was seeking to wrong position, correcting.", request.Type, context.GroupId.ToString(), session.Id);
                     return;
                     return;
-                } else {
+                }
+                else
+                {
                     // Session is ready.
                     // Session is ready.
                     context.SetBuffering(session, false);
                     context.SetBuffering(session, false);
                 }
                 }
@@ -554,28 +541,27 @@ namespace MediaBrowser.Controller.SyncPlay
                 if (!context.IsBuffering())
                 if (!context.IsBuffering())
                 {
                 {
                     // Group is ready, returning to previous state.
                     // Group is ready, returning to previous state.
-                    var pausedState = new PausedGroupState(_logger);
+                    var pausedState = new PausedGroupState(Logger);
                     context.SetState(pausedState);
                     context.SetState(pausedState);
 
 
-                    if (InitialState.Equals(GroupState.Playing))
+                    if (InitialState.Equals(GroupStateType.Playing))
                     {
                     {
                         // Group went from playing to waiting state and a pause request occured while waiting.
                         // Group went from playing to waiting state and a pause request occured while waiting.
                         var pauserequest = new PauseGroupRequest();
                         var pauserequest = new PauseGroupRequest();
-                        pausedState.HandleRequest(context, GetGroupState(), pauserequest, session, cancellationToken);
+                        pausedState.HandleRequest(context, Type, pauserequest, session, cancellationToken);
                     }
                     }
-                    else if (InitialState.Equals(GroupState.Paused))
+                    else if (InitialState.Equals(GroupStateType.Paused))
                     {
                     {
-                        pausedState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
+                        pausedState.HandleRequest(context, Type, request, session, cancellationToken);
                     }
                     }
 
 
-                    _logger.LogDebug("HandleRequest: {0} in group {1}, {2} is ready, returning to previous state.",
-                        request.GetRequestType(), context.GroupId.ToString(), session.Id);
+                    Logger.LogDebug("HandleRequest: {0} in group {1}, {2} is ready, returning to previous state.", request.Type, context.GroupId.ToString(), session.Id);
                 }
                 }
             }
             }
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -587,9 +573,9 @@ namespace MediaBrowser.Controller.SyncPlay
             ResumePlaying = true;
             ResumePlaying = true;
 
 
             // Make sure the client knows the playing item, to avoid duplicate requests.
             // Make sure the client knows the playing item, to avoid duplicate requests.
-            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
+            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase))
             {
             {
-                _logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.Type, context.GroupId.ToString());
                 return;
                 return;
             }
             }
 
 
@@ -607,28 +593,21 @@ namespace MediaBrowser.Controller.SyncPlay
             else
             else
             {
             {
                 // Return to old state.
                 // Return to old state.
-                ISyncPlayState newState;
-                switch (prevState)
+                IGroupState newState = prevState switch
                 {
                 {
-                    case GroupState.Playing:
-                        newState = new PlayingGroupState(_logger);
-                        break;
-                    case GroupState.Paused:
-                        newState = new PausedGroupState(_logger);
-                        break;
-                    default:
-                        newState = new IdleGroupState(_logger);
-                        break;
-                }
+                    GroupStateType.Playing => new PlayingGroupState(Logger),
+                    GroupStateType.Paused => new PausedGroupState(Logger),
+                    _ => new IdleGroupState(Logger)
+                };
 
 
                 context.SetState(newState);
                 context.SetState(newState);
 
 
-                _logger.LogDebug("HandleRequest: {0} in group {1}, no next track available.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogDebug("HandleRequest: {0} in group {1}, no next track available.", request.Type, context.GroupId.ToString());
             }
             }
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+        public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
         {
         {
             // Save state if first event.
             // Save state if first event.
             if (!InitialStateSet)
             if (!InitialStateSet)
@@ -640,9 +619,9 @@ namespace MediaBrowser.Controller.SyncPlay
             ResumePlaying = true;
             ResumePlaying = true;
 
 
             // Make sure the client knows the playing item, to avoid duplicate requests.
             // Make sure the client knows the playing item, to avoid duplicate requests.
-            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
+            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase))
             {
             {
-                _logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.Type, context.GroupId.ToString());
                 return;
                 return;
             }
             }
 
 
@@ -660,23 +639,16 @@ namespace MediaBrowser.Controller.SyncPlay
             else
             else
             {
             {
                 // Return to old state.
                 // Return to old state.
-                ISyncPlayState newState;
-                switch (prevState)
+                IGroupState newState = prevState switch
                 {
                 {
-                    case GroupState.Playing:
-                        newState = new PlayingGroupState(_logger);
-                        break;
-                    case GroupState.Paused:
-                        newState = new PausedGroupState(_logger);
-                        break;
-                    default:
-                        newState = new IdleGroupState(_logger);
-                        break;
-                }
+                    GroupStateType.Playing => new PlayingGroupState(Logger),
+                    GroupStateType.Paused => new PausedGroupState(Logger),
+                    _ => new IdleGroupState(Logger)
+                };
 
 
                 context.SetState(newState);
                 context.SetState(newState);
 
 
-                _logger.LogDebug("HandleRequest: {0} in group {1}, no previous track available.", request.GetRequestType(), context.GroupId.ToString());
+                Logger.LogDebug("HandleRequest: {0} in group {1}, no previous track available.", request.Type, context.GroupId.ToString());
             }
             }
         }
         }
     }
     }

+ 4 - 5
MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs → MediaBrowser.Controller/SyncPlay/IGroupController.cs

@@ -7,9 +7,9 @@ using MediaBrowser.Model.SyncPlay;
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
-    /// Interface ISyncPlayGroupController.
+    /// Interface IGroupController.
     /// </summary>
     /// </summary>
-    public interface ISyncPlayGroupController
+    public interface IGroupController
     {
     {
         /// <summary>
         /// <summary>
         /// Gets the group identifier.
         /// Gets the group identifier.
@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <summary>
         /// <summary>
         /// Checks if the group is empty.
         /// Checks if the group is empty.
         /// </summary>
         /// </summary>
-        /// <returns><c>true</c> if the group is empty, <c>false</c> otherwise</returns>
+        /// <returns><c>true</c> if the group is empty, <c>false</c> otherwise.</returns>
         bool IsGroupEmpty();
         bool IsGroupEmpty();
 
 
         /// <summary>
         /// <summary>
@@ -66,7 +66,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="request">The requested action.</param>
         /// <param name="request">The requested action.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken);
+        void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Gets the info about the group for the clients.
         /// Gets the info about the group for the clients.
@@ -80,6 +80,5 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <returns><c>true</c> if the user can access the play queue; <c>false</c> otherwise.</returns>
         /// <returns><c>true</c> if the user can access the play queue; <c>false</c> otherwise.</returns>
         bool HasAccessToPlayQueue(User user);
         bool HasAccessToPlayQueue(User user);
-
     }
     }
 }
 }

+ 27 - 0
MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs

@@ -0,0 +1,27 @@
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+    /// <summary>
+    /// Interface IGroupPlaybackRequest.
+    /// </summary>
+    public interface IGroupPlaybackRequest
+    {
+        /// <summary>
+        /// Gets the playback request type.
+        /// </summary>
+        /// <returns>The playback request type.</returns>
+        PlaybackRequestType Type { get; }
+
+        /// <summary>
+        /// Applies the request to a group.
+        /// </summary>
+        /// <param name="context">The context of the state.</param>
+        /// <param name="state">The current state.</param>
+        /// <param name="session">The session.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken);
+    }
+}

+ 26 - 26
MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs → MediaBrowser.Controller/SyncPlay/IGroupState.cs

@@ -1,19 +1,19 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
-    /// Interface ISyncPlayState.
+    /// Interface IGroupState.
     /// </summary>
     /// </summary>
-    public interface ISyncPlayState
+    public interface IGroupState
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets the group state.
+        /// Gets the group state type.
         /// </summary>
         /// </summary>
-        /// <value>The group state.</value>
-        GroupState GetGroupState();
+        /// <value>The group state type.</value>
+        GroupStateType Type { get; }
 
 
         /// <summary>
         /// <summary>
         /// Handles a session that joined the group.
         /// Handles a session that joined the group.
@@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="prevState">The previous state.</param>
         /// <param name="prevState">The previous state.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken);
+        void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a session that is leaving the group.
         /// Handles a session that is leaving the group.
@@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="prevState">The previous state.</param>
         /// <param name="prevState">The previous state.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken);
+        void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Generic handle. Context's state can change.
         /// Generic handle. Context's state can change.
@@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The generic action.</param>
         /// <param name="request">The generic action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, IGroupPlaybackRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a play action requested by a session. Context's state can change.
         /// Handles a play action requested by a session. Context's state can change.
@@ -51,7 +51,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The play action.</param>
         /// <param name="request">The play action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a playlist-item change requested by a session. Context's state can change.
         /// Handles a playlist-item change requested by a session. Context's state can change.
@@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The playlist-item change action.</param>
         /// <param name="request">The playlist-item change action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a remove-items change requested by a session. Context's state can change.
         /// Handles a remove-items change requested by a session. Context's state can change.
@@ -71,7 +71,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The remove-items change action.</param>
         /// <param name="request">The remove-items change action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, RemoveFromPlaylistGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, RemoveFromPlaylistGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a move-item change requested by a session. Context's state should not change.
         /// Handles a move-item change requested by a session. Context's state should not change.
@@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The move-item change action.</param>
         /// <param name="request">The move-item change action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, MovePlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, MovePlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a queue change requested by a session. Context's state should not change.
         /// Handles a queue change requested by a session. Context's state should not change.
@@ -91,7 +91,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The queue action.</param>
         /// <param name="request">The queue action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, QueueGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, QueueGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles an unpause action requested by a session. Context's state can change.
         /// Handles an unpause action requested by a session. Context's state can change.
@@ -101,7 +101,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The unpause action.</param>
         /// <param name="request">The unpause action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a pause action requested by a session. Context's state can change.
         /// Handles a pause action requested by a session. Context's state can change.
@@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The pause action.</param>
         /// <param name="request">The pause action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a stop action requested by a session. Context's state can change.
         /// Handles a stop action requested by a session. Context's state can change.
@@ -121,7 +121,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The stop action.</param>
         /// <param name="request">The stop action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a seek action requested by a session. Context's state can change.
         /// Handles a seek action requested by a session. Context's state can change.
@@ -131,7 +131,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The seek action.</param>
         /// <param name="request">The seek action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a buffering action requested by a session. Context's state can change.
         /// Handles a buffering action requested by a session. Context's state can change.
@@ -141,7 +141,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The buffering action.</param>
         /// <param name="request">The buffering action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a buffering-done action requested by a session. Context's state can change.
         /// Handles a buffering-done action requested by a session. Context's state can change.
@@ -151,7 +151,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The buffering-done action.</param>
         /// <param name="request">The buffering-done action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a next-track action requested by a session. Context's state can change.
         /// Handles a next-track action requested by a session. Context's state can change.
@@ -161,7 +161,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The next-track action.</param>
         /// <param name="request">The next-track action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a previous-track action requested by a session. Context's state can change.
         /// Handles a previous-track action requested by a session. Context's state can change.
@@ -171,7 +171,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The previous-track action.</param>
         /// <param name="request">The previous-track action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a repeat-mode change requested by a session. Context's state should not change.
         /// Handles a repeat-mode change requested by a session. Context's state should not change.
@@ -181,7 +181,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The repeat-mode action.</param>
         /// <param name="request">The repeat-mode action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetRepeatModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetRepeatModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Handles a shuffle-mode change requested by a session. Context's state should not change.
         /// Handles a shuffle-mode change requested by a session. Context's state should not change.
@@ -191,7 +191,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The shuffle-mode action.</param>
         /// <param name="request">The shuffle-mode action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetShuffleModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetShuffleModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Updates ping of a session. Context's state should not change.
         /// Updates ping of a session. Context's state should not change.
@@ -201,7 +201,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The buffering-done action.</param>
         /// <param name="request">The buffering-done action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Updates whether the session should be considered during group wait. Context's state should not change.
         /// Updates whether the session should be considered during group wait. Context's state should not change.
@@ -211,6 +211,6 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="request">The ignore-wait action.</param>
         /// <param name="request">The ignore-wait action.</param>
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
+        void HandleRequest(IGroupStateContext context, GroupStateType prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken);
     }
     }
 }
 }

+ 10 - 7
MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs → MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs

@@ -1,15 +1,16 @@
 using System;
 using System;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
-    /// Interface ISyncPlayStateContext.
+    /// Interface IGroupStateContext.
     /// </summary>
     /// </summary>
-    public interface ISyncPlayStateContext
+    public interface IGroupStateContext
     {
     {
         /// <summary>
         /// <summary>
         /// Gets the default ping value used for sessions, in milliseconds.
         /// Gets the default ping value used for sessions, in milliseconds.
@@ -57,11 +58,12 @@ namespace MediaBrowser.Controller.SyncPlay
         /// Sets a new state.
         /// Sets a new state.
         /// </summary>
         /// </summary>
         /// <param name="state">The new state.</param>
         /// <param name="state">The new state.</param>
-        void SetState(ISyncPlayState state);
+        void SetState(IGroupState state);
 
 
         /// <summary>
         /// <summary>
         /// Sends a GroupUpdate message to the interested sessions.
         /// Sends a GroupUpdate message to the interested sessions.
         /// </summary>
         /// </summary>
+        /// <typeparam name="T">The type of the data of the message.</typeparam>
         /// <param name="from">The current session.</param>
         /// <param name="from">The current session.</param>
         /// <param name="type">The filtering type.</param>
         /// <param name="type">The filtering type.</param>
         /// <param name="message">The message to send.</param>
         /// <param name="message">The message to send.</param>
@@ -89,6 +91,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <summary>
         /// <summary>
         /// Builds a new group update message.
         /// Builds a new group update message.
         /// </summary>
         /// </summary>
+        /// <typeparam name="T">The type of the data of the message.</typeparam>
         /// <param name="type">The update type.</param>
         /// <param name="type">The update type.</param>
         /// <param name="data">The data to send.</param>
         /// <param name="data">The data to send.</param>
         /// <returns>The group update.</returns>
         /// <returns>The group update.</returns>
@@ -154,7 +157,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="playingItemPosition">The playing item position in the play queue.</param>
         /// <param name="playingItemPosition">The playing item position in the play queue.</param>
         /// <param name="startPositionTicks">The start position ticks.</param>
         /// <param name="startPositionTicks">The start position ticks.</param>
         /// <returns><c>true</c> if the play queue has been changed; <c>false</c> if something went wrong.</returns>
         /// <returns><c>true</c> if the play queue has been changed; <c>false</c> if something went wrong.</returns>
-        bool SetPlayQueue(Guid[] playQueue, int playingItemPosition, long startPositionTicks);
+        bool SetPlayQueue(IEnumerable<Guid> playQueue, int playingItemPosition, long startPositionTicks);
 
 
         /// <summary>
         /// <summary>
         /// Sets the playing item.
         /// Sets the playing item.
@@ -168,7 +171,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// </summary>
         /// </summary>
         /// <param name="playlistItemIds">The items to remove.</param>
         /// <param name="playlistItemIds">The items to remove.</param>
         /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns>
         /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns>
-        bool RemoveFromPlayQueue(string[] playlistItemIds);
+        bool RemoveFromPlayQueue(IEnumerable<string> playlistItemIds);
 
 
         /// <summary>
         /// <summary>
         /// Moves an item in the play queue.
         /// Moves an item in the play queue.
@@ -184,7 +187,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="newItems">The new items to add to the play queue.</param>
         /// <param name="newItems">The new items to add to the play queue.</param>
         /// <param name="mode">The mode with which the items will be added.</param>
         /// <param name="mode">The mode with which the items will be added.</param>
         /// <returns><c>true</c> if the play queue has been changed; <c>false</c> if something went wrong.</returns>
         /// <returns><c>true</c> if the play queue has been changed; <c>false</c> if something went wrong.</returns>
-        bool AddToPlayQueue(Guid[] newItems, string mode);
+        bool AddToPlayQueue(IEnumerable<Guid> newItems, string mode);
 
 
         /// <summary>
         /// <summary>
         /// Restarts current item in play queue.
         /// Restarts current item in play queue.

+ 0 - 23
MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs

@@ -1,23 +0,0 @@
-using System.Threading;
-using MediaBrowser.Model.SyncPlay;
-using MediaBrowser.Controller.Session;
-
-namespace MediaBrowser.Controller.SyncPlay
-{
-    /// <summary>
-    /// Interface IPlaybackGroupRequest.
-    /// </summary>
-    public interface IPlaybackGroupRequest
-    {
-        /// <summary>
-        /// Gets the playback request type.
-        /// </summary>
-        /// <returns>The playback request type.</returns>
-        PlaybackRequestType GetRequestType();
-
-        /// <summary>
-        /// Applies the request to a group.
-        /// </summary>
-        void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken);
-    }
-}

+ 3 - 3
MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs

@@ -48,7 +48,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken);
+        void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Maps a session to a group.
         /// Maps a session to a group.
@@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="group">The group.</param>
         /// <param name="group">The group.</param>
         /// <exception cref="InvalidOperationException">Thrown when the user is in another group already.</exception>
         /// <exception cref="InvalidOperationException">Thrown when the user is in another group already.</exception>
-        void AddSessionToGroup(SessionInfo session, ISyncPlayGroupController group);
+        void AddSessionToGroup(SessionInfo session, IGroupController group);
 
 
         /// <summary>
         /// <summary>
         /// Unmaps a session from a group.
         /// Unmaps a session from a group.
@@ -64,6 +64,6 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="session">The session.</param>
         /// <param name="session">The session.</param>
         /// <param name="group">The group.</param>
         /// <param name="group">The group.</param>
         /// <exception cref="InvalidOperationException">Thrown when the user is not found in the specified group.</exception>
         /// <exception cref="InvalidOperationException">Thrown when the user is not found in the specified group.</exception>
-        void RemoveSessionFromGroup(SessionInfo session, ISyncPlayGroupController group);
+        void RemoveSessionFromGroup(SessionInfo session, IGroupController group);
     }
     }
 }
 }

+ 0 - 30
MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs

@@ -1,30 +0,0 @@
-using System.Threading;
-using MediaBrowser.Model.SyncPlay;
-using MediaBrowser.Controller.Session;
-
-namespace MediaBrowser.Controller.SyncPlay
-{
-    /// <summary>
-    /// Class IgnoreWaitGroupRequest.
-    /// </summary>
-    public class IgnoreWaitGroupRequest : IPlaybackGroupRequest
-    {
-        /// <summary>
-        /// Gets or sets the client group-wait status.
-        /// </summary>
-        /// <value>The client group-wait status.</value>
-        public bool IgnoreWait { get; set; }
-
-        /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.IgnoreWait;
-        }
-
-        /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
-        {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
-        }
-    }
-}

+ 0 - 24
MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs

@@ -1,24 +0,0 @@
-using System.Threading;
-using MediaBrowser.Model.SyncPlay;
-using MediaBrowser.Controller.Session;
-
-namespace MediaBrowser.Controller.SyncPlay
-{
-    /// <summary>
-    /// Class PauseGroupRequest.
-    /// </summary>
-    public class PauseGroupRequest : IPlaybackGroupRequest
-    {
-        /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Pause;
-        }
-
-        /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
-        {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
-        }
-    }
-}

+ 0 - 30
MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs

@@ -1,30 +0,0 @@
-using System.Threading;
-using MediaBrowser.Model.SyncPlay;
-using MediaBrowser.Controller.Session;
-
-namespace MediaBrowser.Controller.SyncPlay
-{
-    /// <summary>
-    /// Class RemoveFromPlaylistGroupRequest.
-    /// </summary>
-    public class RemoveFromPlaylistGroupRequest : IPlaybackGroupRequest
-    {
-        /// <summary>
-        /// Gets or sets the playlist identifiers ot the items.
-        /// </summary>
-        /// <value>The playlist identifiers ot the items.</value>
-        public string[] PlaylistItemIds { get; set; }
-
-        /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.RemoveFromPlaylist;
-        }
-
-        /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
-        {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
-        }
-    }
-}

+ 0 - 24
MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs

@@ -1,24 +0,0 @@
-using System.Threading;
-using MediaBrowser.Model.SyncPlay;
-using MediaBrowser.Controller.Session;
-
-namespace MediaBrowser.Controller.SyncPlay
-{
-    /// <summary>
-    /// Class StopGroupRequest.
-    /// </summary>
-    public class StopGroupRequest : IPlaybackGroupRequest
-    {
-        /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Stop;
-        }
-
-        /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
-        {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
-        }
-    }
-}

+ 0 - 24
MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs

@@ -1,24 +0,0 @@
-using System.Threading;
-using MediaBrowser.Model.SyncPlay;
-using MediaBrowser.Controller.Session;
-
-namespace MediaBrowser.Controller.SyncPlay
-{
-    /// <summary>
-    /// Class UnpauseGroupRequest.
-    /// </summary>
-    public class UnpauseGroupRequest : IPlaybackGroupRequest
-    {
-        /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Unpause;
-        }
-
-        /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
-        {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
-        }
-    }
-}

+ 6 - 9
MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs

@@ -1,14 +1,14 @@
 using System;
 using System;
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class BufferGroupRequest.
     /// Class BufferGroupRequest.
     /// </summary>
     /// </summary>
-    public class BufferGroupRequest : IPlaybackGroupRequest
+    public class BufferGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets when the request has been made by the client.
         /// Gets or sets when the request has been made by the client.
@@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.SyncPlay
         public long PositionTicks { get; set; }
         public long PositionTicks { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the client playback status.
+        /// Gets or sets a value indicating whether the client playback is unpaused.
         /// </summary>
         /// </summary>
         /// <value>The client playback status.</value>
         /// <value>The client playback status.</value>
         public bool IsPlaying { get; set; }
         public bool IsPlaying { get; set; }
@@ -35,15 +35,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public string PlaylistItemId { get; set; }
         public string PlaylistItemId { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Buffer;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Buffer;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 27 - 0
MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs

@@ -0,0 +1,27 @@
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+    /// <summary>
+    /// Class IgnoreWaitGroupRequest.
+    /// </summary>
+    public class IgnoreWaitGroupRequest : IGroupPlaybackRequest
+    {
+        /// <summary>
+        /// Gets or sets a value indicating whether the client should be ignored.
+        /// </summary>
+        /// <value>The client group-wait status.</value>
+        public bool IgnoreWait { get; set; }
+
+        /// <inheritdoc />
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.IgnoreWait;
+
+        /// <inheritdoc />
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
+        {
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
+        }
+    }
+}

+ 5 - 8
MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs

@@ -1,13 +1,13 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class MovePlaylistItemGroupRequest.
     /// Class MovePlaylistItemGroupRequest.
     /// </summary>
     /// </summary>
-    public class MovePlaylistItemGroupRequest : IPlaybackGroupRequest
+    public class MovePlaylistItemGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the playlist identifier of the item.
         /// Gets or sets the playlist identifier of the item.
@@ -22,15 +22,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public int NewIndex { get; set; }
         public int NewIndex { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.MovePlaylistItem;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.MovePlaylistItem;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 5 - 8
MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextTrackGroupRequest.cs

@@ -1,13 +1,13 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class NextTrackGroupRequest.
     /// Class NextTrackGroupRequest.
     /// </summary>
     /// </summary>
-    public class NextTrackGroupRequest : IPlaybackGroupRequest
+    public class NextTrackGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the playing item identifier.
         /// Gets or sets the playing item identifier.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public string PlaylistItemId { get; set; }
         public string PlaylistItemId { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.NextTrack;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.NextTrack;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 21 - 0
MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs

@@ -0,0 +1,21 @@
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+    /// <summary>
+    /// Class PauseGroupRequest.
+    /// </summary>
+    public class PauseGroupRequest : IGroupPlaybackRequest
+    {
+        /// <inheritdoc />
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Pause;
+
+        /// <inheritdoc />
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
+        {
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
+        }
+    }
+}

+ 5 - 8
MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs

@@ -1,13 +1,13 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class PingGroupRequest.
     /// Class PingGroupRequest.
     /// </summary>
     /// </summary>
-    public class PingGroupRequest : IPlaybackGroupRequest
+    public class PingGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the ping time.
         /// Gets or sets the ping time.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public long Ping { get; set; }
         public long Ping { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Ping;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Ping;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 8 - 10
MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs

@@ -1,20 +1,21 @@
 using System;
 using System;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class PlayGroupRequest.
     /// Class PlayGroupRequest.
     /// </summary>
     /// </summary>
-    public class PlayGroupRequest : IPlaybackGroupRequest
+    public class PlayGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets or sets the playing queue.
+        /// Gets the playing queue.
         /// </summary>
         /// </summary>
         /// <value>The playing queue.</value>
         /// <value>The playing queue.</value>
-        public Guid[] PlayingQueue { get; set; }
+        public List<Guid> PlayingQueue { get; } = new List<Guid>();
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the playing item from the queue.
         /// Gets or sets the playing item from the queue.
@@ -29,15 +30,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public long StartPositionTicks { get; set; }
         public long StartPositionTicks { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Play;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Play;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 5 - 8
MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousTrackGroupRequest.cs

@@ -1,13 +1,13 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class PreviousTrackGroupRequest.
     /// Class PreviousTrackGroupRequest.
     /// </summary>
     /// </summary>
-    public class PreviousTrackGroupRequest : IPlaybackGroupRequest
+    public class PreviousTrackGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the playing item identifier.
         /// Gets or sets the playing item identifier.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public string PlaylistItemId { get; set; }
         public string PlaylistItemId { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.PreviousTrack;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.PreviousTrack;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 8 - 10
MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs

@@ -1,20 +1,21 @@
 using System;
 using System;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class QueueGroupRequest.
     /// Class QueueGroupRequest.
     /// </summary>
     /// </summary>
-    public class QueueGroupRequest : IPlaybackGroupRequest
+    public class QueueGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets or sets the items to queue.
+        /// Gets the items to queue.
         /// </summary>
         /// </summary>
         /// <value>The items to queue.</value>
         /// <value>The items to queue.</value>
-        public Guid[] ItemIds { get; set; }
+        public List<Guid> ItemIds { get; } = new List<Guid>();
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the mode in which to add the new items.
         /// Gets or sets the mode in which to add the new items.
@@ -23,15 +24,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public string Mode { get; set; }
         public string Mode { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Queue;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Queue;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 6 - 9
MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs

@@ -1,14 +1,14 @@
 using System;
 using System;
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class ReadyGroupRequest.
     /// Class ReadyGroupRequest.
     /// </summary>
     /// </summary>
-    public class ReadyGroupRequest : IPlaybackGroupRequest
+    public class ReadyGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets when the request has been made by the client.
         /// Gets or sets when the request has been made by the client.
@@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.SyncPlay
         public long PositionTicks { get; set; }
         public long PositionTicks { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the client playback status.
+        /// Gets or sets a value indicating whether the client playback is unpaused.
         /// </summary>
         /// </summary>
         /// <value>The client playback status.</value>
         /// <value>The client playback status.</value>
         public bool IsPlaying { get; set; }
         public bool IsPlaying { get; set; }
@@ -35,15 +35,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public string PlaylistItemId { get; set; }
         public string PlaylistItemId { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Ready;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Ready;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 28 - 0
MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs

@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+    /// <summary>
+    /// Class RemoveFromPlaylistGroupRequest.
+    /// </summary>
+    public class RemoveFromPlaylistGroupRequest : IGroupPlaybackRequest
+    {
+        /// <summary>
+        /// Gets the playlist identifiers ot the items.
+        /// </summary>
+        /// <value>The playlist identifiers ot the items.</value>
+        public List<string> PlaylistItemIds { get; } = new List<string>();
+
+        /// <inheritdoc />
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.RemoveFromPlaylist;
+
+        /// <inheritdoc />
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
+        {
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
+        }
+    }
+}

+ 5 - 8
MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs

@@ -1,13 +1,13 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class SeekGroupRequest.
     /// Class SeekGroupRequest.
     /// </summary>
     /// </summary>
-    public class SeekGroupRequest : IPlaybackGroupRequest
+    public class SeekGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the position ticks.
         /// Gets or sets the position ticks.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public long PositionTicks { get; set; }
         public long PositionTicks { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.Seek;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Seek;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 5 - 8
MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs

@@ -1,13 +1,13 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class SetPlaylistItemGroupRequest.
     /// Class SetPlaylistItemGroupRequest.
     /// </summary>
     /// </summary>
-    public class SetPlaylistItemGroupRequest : IPlaybackGroupRequest
+    public class SetPlaylistItemGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the playlist identifier of the playing item.
         /// Gets or sets the playlist identifier of the playing item.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public string PlaylistItemId { get; set; }
         public string PlaylistItemId { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.SetPlaylistItem;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.SetPlaylistItem;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 5 - 8
MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs

@@ -1,13 +1,13 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class SetRepeatModeGroupRequest.
     /// Class SetRepeatModeGroupRequest.
     /// </summary>
     /// </summary>
-    public class SetRepeatModeGroupRequest : IPlaybackGroupRequest
+    public class SetRepeatModeGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the repeat mode.
         /// Gets or sets the repeat mode.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public string Mode { get; set; }
         public string Mode { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.SetRepeatMode;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.SetRepeatMode;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 5 - 8
MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs → MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs

@@ -1,13 +1,13 @@
 using System.Threading;
 using System.Threading;
-using MediaBrowser.Model.SyncPlay;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
     /// Class SetShuffleModeGroupRequest.
     /// Class SetShuffleModeGroupRequest.
     /// </summary>
     /// </summary>
-    public class SetShuffleModeGroupRequest : IPlaybackGroupRequest
+    public class SetShuffleModeGroupRequest : IGroupPlaybackRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the shuffle mode.
         /// Gets or sets the shuffle mode.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
         public string Mode { get; set; }
         public string Mode { get; set; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public PlaybackRequestType GetRequestType()
-        {
-            return PlaybackRequestType.SetShuffleMode;
-        }
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.SetShuffleMode;
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken)
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
         {
         {
-            state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken);
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
         }
         }
     }
     }
 }
 }

+ 21 - 0
MediaBrowser.Controller/SyncPlay/PlaybackRequests/StopGroupRequest.cs

@@ -0,0 +1,21 @@
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+    /// <summary>
+    /// Class StopGroupRequest.
+    /// </summary>
+    public class StopGroupRequest : IGroupPlaybackRequest
+    {
+        /// <inheritdoc />
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Stop;
+
+        /// <inheritdoc />
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
+        {
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
+        }
+    }
+}

+ 21 - 0
MediaBrowser.Controller/SyncPlay/PlaybackRequests/UnpauseGroupRequest.cs

@@ -0,0 +1,21 @@
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+    /// <summary>
+    /// Class UnpauseGroupRequest.
+    /// </summary>
+    public class UnpauseGroupRequest : IGroupPlaybackRequest
+    {
+        /// <inheritdoc />
+        public PlaybackRequestType Type { get; } = PlaybackRequestType.Unpause;
+
+        /// <inheritdoc />
+        public void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken)
+        {
+            state.HandleRequest(context, state.Type, this, session, cancellationToken);
+        }
+    }
+}

+ 180 - 135
MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs

@@ -5,163 +5,96 @@ using MediaBrowser.Model.SyncPlay;
 
 
 namespace MediaBrowser.Controller.SyncPlay
 namespace MediaBrowser.Controller.SyncPlay
 {
 {
-    static class ListShuffleExtension
-    {
-        private static Random rng = new Random();
-        public static void Shuffle<T>(this IList<T> list)
-        {
-            int n = list.Count;
-            while (n > 1)
-            {
-                n--;
-                int k = rng.Next(n + 1);
-                T value = list[k];
-                list[k] = list[n];
-                list[n] = value;
-            }
-        }
-    }
-
     /// <summary>
     /// <summary>
     /// Class PlayQueueManager.
     /// Class PlayQueueManager.
     /// </summary>
     /// </summary>
     public class PlayQueueManager
     public class PlayQueueManager
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets or sets the playing item index.
+        /// Placeholder index for when no item is playing.
         /// </summary>
         /// </summary>
-        /// <value>The playing item index.</value>
-        public int PlayingItemIndex { get; private set; }
+        /// <value>The no-playing item index.</value>
+        private const int NoPlayingItemIndex = -1;
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the last time the queue has been changed.
+        /// Random number generator used to shuffle lists.
         /// </summary>
         /// </summary>
-        /// <value>The last time the queue has been changed.</value>
-        public DateTime LastChange { get; private set; }
+        /// <value>The random number generator.</value>
+        private readonly Random randomNumberGenerator = new Random();
 
 
         /// <summary>
         /// <summary>
-        /// Gets the sorted playlist.
+        /// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
         /// </summary>
         /// </summary>
-        /// <value>The sorted playlist, or play queue of the group.</value>
-        private List<QueueItem> SortedPlaylist { get; set; } = new List<QueueItem>();
+        public PlayQueueManager()
+        {
+            Reset();
+        }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the shuffled playlist.
+        /// Gets the playing item index.
         /// </summary>
         /// </summary>
-        /// <value>The shuffled playlist, or play queue of the group.</value>
-        private List<QueueItem> ShuffledPlaylist { get; set; } = new List<QueueItem>();
+        /// <value>The playing item index.</value>
+        public int PlayingItemIndex { get; private set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the shuffle mode.
+        /// Gets the last time the queue has been changed.
+        /// </summary>
+        /// <value>The last time the queue has been changed.</value>
+        public DateTime LastChange { get; private set; }
+
+        /// <summary>
+        /// Gets the shuffle mode.
         /// </summary>
         /// </summary>
         /// <value>The shuffle mode.</value>
         /// <value>The shuffle mode.</value>
         public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted;
         public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted;
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the repeat mode.
+        /// Gets the repeat mode.
         /// </summary>
         /// </summary>
         /// <value>The repeat mode.</value>
         /// <value>The repeat mode.</value>
         public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone;
         public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone;
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the progressive identifier counter.
+        /// Gets or sets the sorted playlist.
         /// </summary>
         /// </summary>
-        /// <value>The progressive identifier.</value>
-        private int ProgressiveId { get; set; } = 0;
-
-        /// <summary>
-        /// Placeholder index for when no item is playing.
-        /// </summary>
-        /// <value>The no-playing item index.</value>
-        private const int NoPlayingItemIndex = -1;
+        /// <value>The sorted playlist, or play queue of the group.</value>
+        private List<QueueItem> SortedPlaylist { get; set; } = new List<QueueItem>();
 
 
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
+        /// Gets or sets the shuffled playlist.
         /// </summary>
         /// </summary>
-        public PlayQueueManager()
-        {
-            Reset();
-        }
+        /// <value>The shuffled playlist, or play queue of the group.</value>
+        private List<QueueItem> ShuffledPlaylist { get; set; } = new List<QueueItem>();
 
 
         /// <summary>
         /// <summary>
-        /// Gets the next available identifier.
+        /// Gets or sets the progressive identifier counter.
         /// </summary>
         /// </summary>
-        /// <returns>The next available identifier.</returns>
-        private int GetNextProgressiveId() {
-            return ProgressiveId++;
-        }
+        /// <value>The progressive identifier.</value>
+        private int ProgressiveId { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Creates a list from the array of items. Each item is given an unique playlist identifier.
+        /// Checks if an item is playing.
         /// </summary>
         /// </summary>
-        /// <returns>The list of queue items.</returns>
-        private List<QueueItem> CreateQueueItemsFromArray(Guid[] items)
+        /// <returns><c>true</c> if an item is playing; <c>false</c> otherwise.</returns>
+        public bool IsItemPlaying()
         {
         {
-            return items.ToList()
-                .Select(item => new QueueItem()
-                {
-                    ItemId = item,
-                    PlaylistItemId = "syncPlayItem" + GetNextProgressiveId()
-                })
-                .ToList();
+            return PlayingItemIndex != NoPlayingItemIndex;
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the current playlist, depending on the shuffle mode.
+        /// Gets the current playlist considering the shuffle mode.
         /// </summary>
         /// </summary>
         /// <returns>The playlist.</returns>
         /// <returns>The playlist.</returns>
-        private List<QueueItem> GetPlaylistAsList()
+        public IReadOnlyList<QueueItem> GetPlaylist()
         {
         {
-            if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
-            {
-                return ShuffledPlaylist;
-            }
-            else
-            {
-                return SortedPlaylist;
-            }
-        }
-
-        /// <summary>
-        /// Gets the current playing item, depending on the shuffle mode.
-        /// </summary>
-        /// <returns>The playing item.</returns>
-        private QueueItem GetPlayingItem()
-        {
-            if (PlayingItemIndex == NoPlayingItemIndex)
-            {
-                return null;
-            }
-            else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
-            {
-                return ShuffledPlaylist[PlayingItemIndex];
-            }
-            else
-            {
-                return SortedPlaylist[PlayingItemIndex];
-            }
-        }
-
-        /// <summary>
-        /// Gets the current playlist as an array, depending on the shuffle mode.
-        /// </summary>
-        /// <returns>The array of items in the playlist.</returns>
-        public QueueItem[] GetPlaylist() {
-            if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
-            {
-                return ShuffledPlaylist.ToArray();
-            }
-            else
-            {
-                return SortedPlaylist.ToArray();
-            }
+            return GetPlaylistInternal();
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Sets a new playlist. Playing item is reset.
         /// Sets a new playlist. Playing item is reset.
         /// </summary>
         /// </summary>
         /// <param name="items">The new items of the playlist.</param>
         /// <param name="items">The new items of the playlist.</param>
-        public void SetPlaylist(Guid[] items)
+        public void SetPlaylist(IEnumerable<Guid> items)
         {
         {
             SortedPlaylist.Clear();
             SortedPlaylist.Clear();
             ShuffledPlaylist.Clear();
             ShuffledPlaylist.Clear();
@@ -170,7 +103,7 @@ namespace MediaBrowser.Controller.SyncPlay
             if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
             if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
             {
             {
                 ShuffledPlaylist = SortedPlaylist.ToList();
                 ShuffledPlaylist = SortedPlaylist.ToList();
-                ShuffledPlaylist.Shuffle();
+                Shuffle(ShuffledPlaylist);
             }
             }
 
 
             PlayingItemIndex = NoPlayingItemIndex;
             PlayingItemIndex = NoPlayingItemIndex;
@@ -181,7 +114,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// Appends new items to the playlist. The specified order is mantained.
         /// Appends new items to the playlist. The specified order is mantained.
         /// </summary>
         /// </summary>
         /// <param name="items">The items to add to the playlist.</param>
         /// <param name="items">The items to add to the playlist.</param>
-        public void Queue(Guid[] items)
+        public void Queue(IEnumerable<Guid> items)
         {
         {
             var newItems = CreateQueueItemsFromArray(items);
             var newItems = CreateQueueItemsFromArray(items);
 
 
@@ -195,13 +128,14 @@ namespace MediaBrowser.Controller.SyncPlay
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Shuffles the playlist. Shuffle mode is changed.
+        /// Shuffles the playlist. Shuffle mode is changed. The playlist gets re-shuffled if already shuffled.
         /// </summary>
         /// </summary>
         public void ShufflePlaylist()
         public void ShufflePlaylist()
         {
         {
-            if (PlayingItemIndex == NoPlayingItemIndex) {
+            if (PlayingItemIndex == NoPlayingItemIndex)
+            {
                 ShuffledPlaylist = SortedPlaylist.ToList();
                 ShuffledPlaylist = SortedPlaylist.ToList();
-                ShuffledPlaylist.Shuffle();
+                Shuffle(ShuffledPlaylist);
             }
             }
             else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
             else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
             {
             {
@@ -209,7 +143,7 @@ namespace MediaBrowser.Controller.SyncPlay
                 var playingItem = SortedPlaylist[PlayingItemIndex];
                 var playingItem = SortedPlaylist[PlayingItemIndex];
                 ShuffledPlaylist = SortedPlaylist.ToList();
                 ShuffledPlaylist = SortedPlaylist.ToList();
                 ShuffledPlaylist.RemoveAt(PlayingItemIndex);
                 ShuffledPlaylist.RemoveAt(PlayingItemIndex);
-                ShuffledPlaylist.Shuffle();
+                Shuffle(ShuffledPlaylist);
                 ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList();
                 ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList();
                 PlayingItemIndex = 0;
                 PlayingItemIndex = 0;
             }
             }
@@ -218,7 +152,7 @@ namespace MediaBrowser.Controller.SyncPlay
                 // Re-shuffle playlist.
                 // Re-shuffle playlist.
                 var playingItem = ShuffledPlaylist[PlayingItemIndex];
                 var playingItem = ShuffledPlaylist[PlayingItemIndex];
                 ShuffledPlaylist.RemoveAt(PlayingItemIndex);
                 ShuffledPlaylist.RemoveAt(PlayingItemIndex);
-                ShuffledPlaylist.Shuffle();
+                Shuffle(ShuffledPlaylist);
                 ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList();
                 ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList();
                 PlayingItemIndex = 0;
                 PlayingItemIndex = 0;
             }
             }
@@ -262,6 +196,7 @@ namespace MediaBrowser.Controller.SyncPlay
                 {
                 {
                     ShuffledPlaylist.Add(playingItem);
                     ShuffledPlaylist.Add(playingItem);
                 }
                 }
+
                 PlayingItemIndex = 0;
                 PlayingItemIndex = 0;
             }
             }
             else
             else
@@ -274,7 +209,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// Adds new items to the playlist right after the playing item. The specified order is mantained.
         /// Adds new items to the playlist right after the playing item. The specified order is mantained.
         /// </summary>
         /// </summary>
         /// <param name="items">The items to add to the playlist.</param>
         /// <param name="items">The items to add to the playlist.</param>
-        public void QueueNext(Guid[] items)
+        public void QueueNext(IEnumerable<Guid> items)
         {
         {
             var newItems = CreateQueueItemsFromArray(items);
             var newItems = CreateQueueItemsFromArray(items);
 
 
@@ -334,8 +269,18 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="itemId">The new playing item identifier.</param>
         /// <param name="itemId">The new playing item identifier.</param>
         public void SetPlayingItemById(Guid itemId)
         public void SetPlayingItemById(Guid itemId)
         {
         {
-            var itemIds = GetPlaylistAsList().Select(queueItem => queueItem.ItemId).ToList();
-            PlayingItemIndex = itemIds.IndexOf(itemId);
+            PlayingItemIndex = NoPlayingItemIndex;
+
+            var playlist = GetPlaylistInternal();
+            foreach (var item in playlist)
+            {
+                if (item.ItemId.Equals(itemId))
+                {
+                    PlayingItemIndex = playlist.IndexOf(item);
+                    break;
+                }
+            }
+
             LastChange = DateTime.UtcNow;
             LastChange = DateTime.UtcNow;
         }
         }
 
 
@@ -346,8 +291,18 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <returns><c>true</c> if playing item has been set; <c>false</c> if item is not in the playlist.</returns>
         /// <returns><c>true</c> if playing item has been set; <c>false</c> if item is not in the playlist.</returns>
         public bool SetPlayingItemByPlaylistId(string playlistItemId)
         public bool SetPlayingItemByPlaylistId(string playlistItemId)
         {
         {
-            var playlistIds = GetPlaylistAsList().Select(queueItem => queueItem.PlaylistItemId).ToList();
-            PlayingItemIndex = playlistIds.IndexOf(playlistItemId);
+            PlayingItemIndex = NoPlayingItemIndex;
+
+            var playlist = GetPlaylistInternal();
+            foreach (var item in playlist)
+            {
+                if (item.PlaylistItemId.Equals(playlistItemId, StringComparison.OrdinalIgnoreCase))
+                {
+                    PlayingItemIndex = playlist.IndexOf(item);
+                    break;
+                }
+            }
+
             LastChange = DateTime.UtcNow;
             LastChange = DateTime.UtcNow;
             return PlayingItemIndex != NoPlayingItemIndex;
             return PlayingItemIndex != NoPlayingItemIndex;
         }
         }
@@ -358,8 +313,8 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <param name="playlistIndex">The new playing item index.</param>
         /// <param name="playlistIndex">The new playing item index.</param>
         public void SetPlayingItemByIndex(int playlistIndex)
         public void SetPlayingItemByIndex(int playlistIndex)
         {
         {
-            var list = GetPlaylistAsList();
-            if (playlistIndex < 0 || playlistIndex > list.Count())
+            var playlist = GetPlaylistInternal();
+            if (playlistIndex < 0 || playlistIndex > playlist.Count)
             {
             {
                 PlayingItemIndex = NoPlayingItemIndex;
                 PlayingItemIndex = NoPlayingItemIndex;
             }
             }
@@ -376,7 +331,7 @@ namespace MediaBrowser.Controller.SyncPlay
         /// </summary>
         /// </summary>
         /// <param name="playlistItemIds">The items to remove.</param>
         /// <param name="playlistItemIds">The items to remove.</param>
         /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns>
         /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns>
-        public bool RemoveFromPlaylist(string[] playlistItemIds)
+        public bool RemoveFromPlaylist(IEnumerable<string> playlistItemIds)
         {
         {
             var playingItem = GetPlayingItem();
             var playingItem = GetPlayingItem();
             var playlistItemIdsList = playlistItemIds.ToList();
             var playlistItemIdsList = playlistItemIds.ToList();
@@ -396,7 +351,7 @@ namespace MediaBrowser.Controller.SyncPlay
                     {
                     {
                         // Was first element, picking next if available.
                         // Was first element, picking next if available.
                         // Default to no playing item otherwise.
                         // Default to no playing item otherwise.
-                        PlayingItemIndex = SortedPlaylist.Count() > 0 ? 0 : NoPlayingItemIndex;
+                        PlayingItemIndex = SortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex;
                     }
                     }
 
 
                     return true;
                     return true;
@@ -422,24 +377,32 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <returns><c>true</c> if the item has been moved; <c>false</c> otherwise.</returns>
         /// <returns><c>true</c> if the item has been moved; <c>false</c> otherwise.</returns>
         public bool MovePlaylistItem(string playlistItemId, int newIndex)
         public bool MovePlaylistItem(string playlistItemId, int newIndex)
         {
         {
-            var list = GetPlaylistAsList();
+            var playlist = GetPlaylistInternal();
             var playingItem = GetPlayingItem();
             var playingItem = GetPlayingItem();
 
 
-            var playlistIds = list.Select(queueItem => queueItem.PlaylistItemId).ToList();
-            var oldIndex = playlistIds.IndexOf(playlistItemId);
+            var oldIndex = -1;
+            foreach (var item in playlist)
+            {
+                if (item.PlaylistItemId.Equals(playlistItemId, StringComparison.OrdinalIgnoreCase))
+                {
+                    oldIndex = playlist.IndexOf(item);
+                    break;
+                }
+            }
+
             if (oldIndex < 0)
             if (oldIndex < 0)
             {
             {
                 return false;
                 return false;
             }
             }
 
 
-            var queueItem = list[oldIndex];
-            list.RemoveAt(oldIndex);
-            newIndex = Math.Min(newIndex, list.Count());
+            var queueItem = playlist[oldIndex];
+            playlist.RemoveAt(oldIndex);
+            newIndex = Math.Min(newIndex, playlist.Count);
             newIndex = Math.Max(newIndex, 0);
             newIndex = Math.Max(newIndex, 0);
-            list.Insert(newIndex, queueItem);
+            playlist.Insert(newIndex, queueItem);
 
 
             LastChange = DateTime.UtcNow;
             LastChange = DateTime.UtcNow;
-            PlayingItemIndex = list.IndexOf(playingItem);
+            PlayingItemIndex = playlist.IndexOf(playingItem);
             return true;
             return true;
         }
         }
 
 
@@ -505,7 +468,7 @@ namespace MediaBrowser.Controller.SyncPlay
         public QueueItem GetNextItemPlaylistId()
         public QueueItem GetNextItemPlaylistId()
         {
         {
             int newIndex;
             int newIndex;
-            var playlist = GetPlaylistAsList();
+            var playlist = GetPlaylistInternal();
 
 
             switch (RepeatMode)
             switch (RepeatMode)
             {
             {
@@ -514,17 +477,18 @@ namespace MediaBrowser.Controller.SyncPlay
                     break;
                     break;
                 case GroupRepeatMode.RepeatAll:
                 case GroupRepeatMode.RepeatAll:
                     newIndex = PlayingItemIndex + 1;
                     newIndex = PlayingItemIndex + 1;
-                    if (newIndex >= playlist.Count())
+                    if (newIndex >= playlist.Count)
                     {
                     {
                         newIndex = 0;
                         newIndex = 0;
                     }
                     }
+
                     break;
                     break;
                 default:
                 default:
                     newIndex = PlayingItemIndex + 1;
                     newIndex = PlayingItemIndex + 1;
                     break;
                     break;
             }
             }
 
 
-            if (newIndex < 0 || newIndex >= playlist.Count())
+            if (newIndex < 0 || newIndex >= playlist.Count)
             {
             {
                 return null;
                 return null;
             }
             }
@@ -545,7 +509,7 @@ namespace MediaBrowser.Controller.SyncPlay
             }
             }
 
 
             PlayingItemIndex++;
             PlayingItemIndex++;
-            if (PlayingItemIndex >= SortedPlaylist.Count())
+            if (PlayingItemIndex >= SortedPlaylist.Count)
             {
             {
                 if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
                 if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
                 {
                 {
@@ -579,7 +543,7 @@ namespace MediaBrowser.Controller.SyncPlay
             {
             {
                 if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
                 if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
                 {
                 {
-                    PlayingItemIndex = SortedPlaylist.Count() - 1;
+                    PlayingItemIndex = SortedPlaylist.Count - 1;
                 }
                 }
                 else
                 else
                 {
                 {
@@ -591,5 +555,86 @@ namespace MediaBrowser.Controller.SyncPlay
             LastChange = DateTime.UtcNow;
             LastChange = DateTime.UtcNow;
             return true;
             return true;
         }
         }
+
+        /// <summary>
+        /// Shuffles a given list.
+        /// </summary>
+        /// <param name="list">The list to shuffle.</param>
+        private void Shuffle<T>(IList<T> list)
+        {
+            int n = list.Count;
+            while (n > 1)
+            {
+                n--;
+                int k = randomNumberGenerator.Next(n + 1);
+                T value = list[k];
+                list[k] = list[n];
+                list[n] = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets the next available identifier.
+        /// </summary>
+        /// <returns>The next available identifier.</returns>
+        private int GetNextProgressiveId()
+        {
+            return ProgressiveId++;
+        }
+
+        /// <summary>
+        /// Creates a list from the array of items. Each item is given an unique playlist identifier.
+        /// </summary>
+        /// <returns>The list of queue items.</returns>
+        private List<QueueItem> CreateQueueItemsFromArray(IEnumerable<Guid> items)
+        {
+            var list = new List<QueueItem>();
+            foreach (var item in items)
+            {
+                list.Add(new QueueItem()
+                {
+                    ItemId = item,
+                    PlaylistItemId = "syncPlayItem" + GetNextProgressiveId()
+                });
+            }
+
+            return list;
+        }
+
+        /// <summary>
+        /// Gets the current playlist considering the shuffle mode.
+        /// </summary>
+        /// <returns>The playlist.</returns>
+        private List<QueueItem> GetPlaylistInternal()
+        {
+            if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
+            {
+                return ShuffledPlaylist;
+            }
+            else
+            {
+                return SortedPlaylist;
+            }
+        }
+
+        /// <summary>
+        /// Gets the current playing item, depending on the shuffle mode.
+        /// </summary>
+        /// <returns>The playing item.</returns>
+        private QueueItem GetPlayingItem()
+        {
+            if (PlayingItemIndex == NoPlayingItemIndex)
+            {
+                return null;
+            }
+            else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
+            {
+                return ShuffledPlaylist[PlayingItemIndex];
+            }
+            else
+            {
+                return SortedPlaylist[PlayingItemIndex];
+            }
+        }
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Model/SyncPlay/GroupInfoDto.cs

@@ -25,7 +25,7 @@ namespace MediaBrowser.Model.SyncPlay
         /// Gets or sets the group state.
         /// Gets or sets the group state.
         /// </summary>
         /// </summary>
         /// <value>The group state.</value>
         /// <value>The group state.</value>
-        public GroupState State { get; set; }
+        public GroupStateType State { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the participants.
         /// Gets or sets the participants.

+ 4 - 1
MediaBrowser.Model/SyncPlay/GroupState.cs → MediaBrowser.Model/SyncPlay/GroupStateType.cs

@@ -3,20 +3,23 @@ namespace MediaBrowser.Model.SyncPlay
     /// <summary>
     /// <summary>
     /// Enum GroupState.
     /// Enum GroupState.
     /// </summary>
     /// </summary>
-    public enum GroupState
+    public enum GroupStateType
     {
     {
         /// <summary>
         /// <summary>
         /// The group is in idle state. No media is playing.
         /// The group is in idle state. No media is playing.
         /// </summary>
         /// </summary>
         Idle,
         Idle,
+
         /// <summary>
         /// <summary>
         /// The group is in wating state. Playback is paused. Will start playing when users are ready.
         /// The group is in wating state. Playback is paused. Will start playing when users are ready.
         /// </summary>
         /// </summary>
         Waiting,
         Waiting,
+
         /// <summary>
         /// <summary>
         /// The group is in paused state. Playback is paused. Will resume on play command.
         /// The group is in paused state. Playback is paused. Will resume on play command.
         /// </summary>
         /// </summary>
         Paused,
         Paused,
+
         /// <summary>
         /// <summary>
         /// The group is in playing state. Playback is advancing.
         /// The group is in playing state. Playback is advancing.
         /// </summary>
         /// </summary>

+ 1 - 3
MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 namespace MediaBrowser.Model.SyncPlay
 namespace MediaBrowser.Model.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
@@ -11,7 +9,7 @@ namespace MediaBrowser.Model.SyncPlay
         /// Gets or sets the state of the group.
         /// Gets or sets the state of the group.
         /// </summary>
         /// </summary>
         /// <value>The state of the group.</value>
         /// <value>The state of the group.</value>
-        public GroupState State { get; set; }
+        public GroupStateType State { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the reason of the state change.
         /// Gets or sets the reason of the state change.

+ 1 - 0
MediaBrowser.Model/SyncPlay/GroupUpdate.cs

@@ -5,6 +5,7 @@ namespace MediaBrowser.Model.SyncPlay
     /// <summary>
     /// <summary>
     /// Class GroupUpdate.
     /// Class GroupUpdate.
     /// </summary>
     /// </summary>
+    /// <typeparam name="T">The type of the data of the message.</typeparam>
     public class GroupUpdate<T>
     public class GroupUpdate<T>
     {
     {
         /// <summary>
         /// <summary>

+ 3 - 1
MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs

@@ -1,5 +1,7 @@
 #nullable disable
 #nullable disable
 
 
+using System.Collections.Generic;
+
 namespace MediaBrowser.Model.SyncPlay
 namespace MediaBrowser.Model.SyncPlay
 {
 {
     /// <summary>
     /// <summary>
@@ -23,7 +25,7 @@ namespace MediaBrowser.Model.SyncPlay
         /// Gets or sets the playlist.
         /// Gets or sets the playlist.
         /// </summary>
         /// </summary>
         /// <value>The playlist.</value>
         /// <value>The playlist.</value>
-        public QueueItem[] Playlist { get; set; }
+        public IReadOnlyList<QueueItem> Playlist { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the playing item index in the playlist.
         /// Gets or sets the playing item index in the playlist.

+ 1 - 0
MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs

@@ -69,6 +69,7 @@ namespace MediaBrowser.Model.SyncPlay
         /// A user is requesting previous track in playlist.
         /// A user is requesting previous track in playlist.
         /// </summary>
         /// </summary>
         PreviousTrack = 12,
         PreviousTrack = 12,
+
         /// <summary>
         /// <summary>
         /// A user is setting the repeat mode.
         /// A user is setting the repeat mode.
         /// </summary>
         /// </summary>