From 8819a9d478e6fc11dbfdcff80d9a2dc175953373 Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca Date: Thu, 24 Sep 2020 23:04:21 +0200 Subject: Add playlist-sync and group-wait to SyncPlay --- MediaBrowser.Controller/Session/ISessionManager.cs | 12 +- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 154 ------ MediaBrowser.Controller/SyncPlay/GroupMember.cs | 20 +- .../SyncPlay/IPlaybackGroupRequest.cs | 7 +- .../SyncPlay/ISyncPlayController.cs | 40 +- .../SyncPlay/ISyncPlayManager.cs | 12 +- MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs | 165 +++++- .../SyncPlay/ISyncPlayStateContext.cs | 160 +++++- .../SyncPlay/PlaybackRequest/BufferGroupRequest.cs | 18 +- .../PlaybackRequest/IgnoreWaitGroupRequest.cs | 30 ++ .../MovePlaylistItemGroupRequest.cs | 36 ++ .../PlaybackRequest/NextTrackGroupRequest.cs | 30 ++ .../SyncPlay/PlaybackRequest/PauseGroupRequest.cs | 6 +- .../SyncPlay/PlaybackRequest/PingGroupRequest.cs | 7 +- .../SyncPlay/PlaybackRequest/PlayGroupRequest.cs | 25 +- .../PlaybackRequest/PreviousTrackGroupRequest.cs | 30 ++ .../SyncPlay/PlaybackRequest/QueueGroupRequest.cs | 37 ++ .../SyncPlay/PlaybackRequest/ReadyGroupRequest.cs | 18 +- .../RemoveFromPlaylistGroupRequest.cs | 30 ++ .../SyncPlay/PlaybackRequest/SeekGroupRequest.cs | 6 +- .../PlaybackRequest/SetCurrentItemGroupRequest.cs | 30 ++ .../PlaybackRequest/SetRepeatModeGroupRequest.cs | 30 ++ .../PlaybackRequest/SetShuffleModeGroupRequest.cs | 30 ++ .../SyncPlay/PlaybackRequest/StopGroupRequest.cs | 24 + .../PlaybackRequest/UnpauseGroupRequest.cs | 24 + .../SyncPlay/Queue/PlayQueueManager.cs | 596 +++++++++++++++++++++ .../SyncPlay/SyncPlayAbstractState.cs | 65 --- 27 files changed, 1331 insertions(+), 311 deletions(-) delete mode 100644 MediaBrowser.Controller/SyncPlay/GroupInfo.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs delete mode 100644 MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 04c3004ee6..9ad8557ce6 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -143,22 +143,22 @@ namespace MediaBrowser.Controller.Session Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken); /// - /// Sends the SyncPlayCommand. + /// Sends a SyncPlayCommand to a session. /// - /// The session id. + /// The session. /// The command. /// The cancellation token. /// Task. - Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); + Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken); /// - /// Sends the SyncPlayGroupUpdate. + /// Sends a SyncPlayGroupUpdate to a session. /// - /// The session id. + /// The session. /// The group update. /// The cancellation token. /// Task. - Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); + Task SendSyncPlayGroupUpdate(SessionInfo session, GroupUpdate command, CancellationToken cancellationToken); /// /// Sends the browse command. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs deleted file mode 100644 index cdd24d0b59..0000000000 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Session; - -namespace MediaBrowser.Controller.SyncPlay -{ - /// - /// Class GroupInfo. - /// - /// - /// Class is not thread-safe, external locking is required when accessing methods. - /// - public class GroupInfo - { - /// - /// The default ping value used for sessions. - /// - public const long DefaultPing = 500; - - /// - /// Gets the group identifier. - /// - /// The group identifier. - public Guid GroupId { get; } = Guid.NewGuid(); - - /// - /// Gets or sets the playing item. - /// - /// The playing item. - public BaseItem PlayingItem { get; set; } - - /// - /// Gets or sets a value indicating whether there are position ticks. - /// - /// The position ticks. - public long PositionTicks { get; set; } - - /// - /// Gets or sets the last activity. - /// - /// The last activity. - public DateTime LastActivity { get; set; } - - /// - /// Gets the participants. - /// - /// The participants, or members of the group. - public Dictionary Participants { get; } = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Checks if a session is in this group. - /// - /// The session id to check. - /// true if the session is in this group; false otherwise. - public bool ContainsSession(string sessionId) - { - return Participants.ContainsKey(sessionId); - } - - /// - /// Adds the session to the group. - /// - /// The session. - public void AddSession(SessionInfo session) - { - Participants.TryAdd( - session.Id, - new GroupMember - { - Session = session, - Ping = DefaultPing, - IsBuffering = false - }); - } - - /// - /// Removes the session from the group. - /// - /// The session. - public void RemoveSession(SessionInfo session) - { - Participants.Remove(session.Id); - } - - /// - /// Updates the ping of a session. - /// - /// The session. - /// The ping. - public void UpdatePing(SessionInfo session, long ping) - { - if (Participants.TryGetValue(session.Id, out GroupMember value)) - { - value.Ping = ping; - } - } - - /// - /// Gets the highest ping in the group. - /// - /// The highest ping in the group. - public long GetHighestPing() - { - long max = long.MinValue; - foreach (var session in Participants.Values) - { - max = Math.Max(max, session.Ping); - } - - return max; - } - - /// - /// Sets the session's buffering state. - /// - /// The session. - /// The state. - public void SetBuffering(SessionInfo session, bool isBuffering) - { - if (Participants.TryGetValue(session.Id, out GroupMember value)) - { - value.IsBuffering = isBuffering; - } - } - - /// - /// Gets the group buffering state. - /// - /// true if there is a session buffering in the group; false otherwise. - public bool IsBuffering() - { - foreach (var session in Participants.Values) - { - if (session.IsBuffering) - { - return true; - } - } - - return false; - } - - /// - /// Checks if the group is empty. - /// - /// true if the group is empty; false otherwise. - public bool IsEmpty() - { - return Participants.Count == 0; - } - } -} diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs index cde6f8e8ce..9a9d30277f 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -7,12 +7,6 @@ namespace MediaBrowser.Controller.SyncPlay /// public class GroupMember { - /// - /// Gets or sets a value indicating whether this member is buffering. - /// - /// true if member is buffering; false otherwise. - public bool IsBuffering { get; set; } - /// /// Gets or sets the session. /// @@ -20,9 +14,21 @@ namespace MediaBrowser.Controller.SyncPlay public SessionInfo Session { get; set; } /// - /// Gets or sets the ping. + /// Gets or sets the ping, in milliseconds. /// /// The ping. public long Ping { get; set; } + + /// + /// Gets or sets a value indicating whether this member is buffering. + /// + /// true if member is buffering; false otherwise. + public bool IsBuffering { get; set; } + + /// + /// Gets or sets a value indicating whether this member is following group playback. + /// + /// true to ignore member on group wait; false if they're following group playback. + public bool IgnoreGroupWait { get; set; } } } diff --git a/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs index a6e87a007f..35ca64c8df 100644 --- a/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs @@ -12,13 +12,12 @@ namespace MediaBrowser.Controller.SyncPlay /// /// Gets the playback request type. /// - /// The playback request type. - PlaybackRequestType Type(); + /// The playback request type. + PlaybackRequestType GetRequestType(); /// /// Applies the request to a group. /// - /// The operation completion status. - bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken); + void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs index 5ac2aeb247..9a4e1ee1ee 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs @@ -1,39 +1,41 @@ using System; using System.Threading; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.SyncPlay { /// - /// Interface ISyncPlayController. + /// Interface ISyncPlayGroupController. /// - public interface ISyncPlayController + public interface ISyncPlayGroupController { /// - /// Gets the group id. + /// Gets the group identifier. /// - /// The group id. - Guid GetGroupId(); + /// The group identifier. + Guid GroupId { get; } /// - /// Gets the playing item id. + /// Gets the play queue. /// - /// The playing item id. - Guid GetPlayingItemId(); + /// The play queue. + PlayQueueManager PlayQueue { get; } /// /// Checks if the group is empty. /// - /// If the group is empty. + /// If the group is empty. bool IsGroupEmpty(); /// /// Initializes the group with the session's info. /// /// The session. + /// The request. /// The cancellation token. - void CreateGroup(SessionInfo session, CancellationToken cancellationToken); + void CreateGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken); /// /// Adds the session to the group. @@ -43,6 +45,14 @@ namespace MediaBrowser.Controller.SyncPlay /// The cancellation token. void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); + /// + /// Restores the state of a session that already joined the group. + /// + /// The session. + /// The request. + /// The cancellation token. + void SessionRestore(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); + /// /// Removes the session from the group. /// @@ -61,7 +71,15 @@ namespace MediaBrowser.Controller.SyncPlay /// /// Gets the info about the group for the clients. /// - /// The group info for the clients. + /// The group info for the clients. GroupInfoDto GetInfo(); + + /// + /// Checks if a user has access to all content in the play queue. + /// + /// The user. + /// true if the user can access the play queue; false otherwise. + bool HasAccessToPlayQueue(User user); + } } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 6fa94e2ce4..9bef3f5593 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -15,8 +15,9 @@ namespace MediaBrowser.Controller.SyncPlay /// Creates a new group. /// /// The session that's creating the group. + /// The request. /// The cancellation token. - void NewGroup(SessionInfo session, CancellationToken cancellationToken); + void NewGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken); /// /// Adds the session to a group. @@ -38,9 +39,8 @@ namespace MediaBrowser.Controller.SyncPlay /// Gets list of available groups for a session. /// /// The session. - /// The item id to filter by. - /// The list of available groups. - List ListGroups(SessionInfo session, Guid filterItemId); + /// The list of available groups. + List ListGroups(SessionInfo session); /// /// Handle a request by a session in a group. @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The session. /// The group. /// - void AddSessionToGroup(SessionInfo session, ISyncPlayController group); + void AddSessionToGroup(SessionInfo session, ISyncPlayGroupController group); /// /// Unmaps a session from a group. @@ -64,6 +64,6 @@ namespace MediaBrowser.Controller.SyncPlay /// The session. /// The group. /// - void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group); + void RemoveSessionFromGroup(SessionInfo session, ISyncPlayGroupController group); } } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs index 55c9ee938f..290576e30f 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs @@ -15,81 +15,202 @@ namespace MediaBrowser.Controller.SyncPlay /// The group state. GroupState GetGroupState(); + /// + /// Handle a session that joined the group. + /// + /// The context of the state. + /// The previous state. + /// The session. + /// The cancellation token. + void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handle a session that is leaving the group. + /// + /// The context of the state. + /// The previous state. + /// The session. + /// The cancellation token. + void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken); + /// /// Generic handle. Context's state can change. /// /// The context of the state. - /// Whether the state has been just set. - /// The play action. + /// The previous state. + /// The generic action. /// The session. /// The cancellation token. - /// The operation completion status. - bool HandleRequest(ISyncPlayStateContext context, bool newState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// /// Handles a play action requested by a session. Context's state can change. /// /// The context of the state. - /// Whether the state has been just set. + /// The previous state. /// The play action. /// The session. /// The cancellation token. - /// The operation completion status. - bool HandleRequest(ISyncPlayStateContext context, bool newState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a playlist-item change requested by a session. Context's state can change. + /// + /// The context of the state. + /// The previous state. + /// The playlist-item change action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a remove-items change requested by a session. Context's state can change. + /// + /// The context of the state. + /// The previous state. + /// The remove-items change action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, RemoveFromPlaylistGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a move-item change requested by a session. Context's state should not change. + /// + /// The context of the state. + /// The previous state. + /// The move-item change action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, MovePlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a queue change requested by a session. Context's state should not change. + /// + /// The context of the state. + /// The previous state. + /// The queue action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, QueueGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles an unpause action requested by a session. Context's state can change. + /// + /// The context of the state. + /// The previous state. + /// The unpause action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// /// Handles a pause action requested by a session. Context's state can change. /// /// The context of the state. - /// Whether the state has been just set. + /// The previous state. /// The pause action. /// The session. /// The cancellation token. - /// The operation completion status. - bool HandleRequest(ISyncPlayStateContext context, bool newState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a stop action requested by a session. Context's state can change. + /// + /// The context of the state. + /// The previous state. + /// The stop action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// /// Handles a seek action requested by a session. Context's state can change. /// /// The context of the state. - /// Whether the state has been just set. + /// The previous state. /// The seek action. /// The session. /// The cancellation token. - /// The operation completion status. - bool HandleRequest(ISyncPlayStateContext context, bool newState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// /// Handles a buffering action requested by a session. Context's state can change. /// /// The context of the state. - /// Whether the state has been just set. + /// The previous state. /// The buffering action. /// The session. /// The cancellation token. - /// The operation completion status. - bool HandleRequest(ISyncPlayStateContext context, bool newState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// /// Handles a buffering-done action requested by a session. Context's state can change. /// /// The context of the state. - /// Whether the state has been just set. + /// The previous state. /// The buffering-done action. /// The session. /// The cancellation token. - /// The operation completion status. - bool HandleRequest(ISyncPlayStateContext context, bool newState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a next-track action requested by a session. Context's state can change. + /// + /// The context of the state. + /// The previous state. + /// The next-track action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a previous-track action requested by a session. Context's state can change. + /// + /// The context of the state. + /// The previous state. + /// The previous-track action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a repeat-mode change requested by a session. Context's state should not change. + /// + /// The context of the state. + /// The previous state. + /// The repeat-mode action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetRepeatModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a shuffle-mode change requested by a session. Context's state should not change. + /// + /// The context of the state. + /// The previous state. + /// The shuffle-mode action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetShuffleModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// /// Updates ping of a session. Context's state should not change. /// /// The context of the state. - /// Whether the state has been just set. + /// The previous state. /// The buffering-done action. /// The session. /// The cancellation token. - /// The operation completion status. - bool HandleRequest(ISyncPlayStateContext context, bool newState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Updates whether the session should be considered during group wait. Context's state should not change. + /// + /// The context of the state. + /// The previous state. + /// The ignore-wait action. + /// The session. + /// The cancellation token. + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs index 9bdb1ace6c..18a6857491 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs @@ -12,10 +12,34 @@ namespace MediaBrowser.Controller.SyncPlay public interface ISyncPlayStateContext { /// - /// Gets the context's group. + /// Gets the default ping value used for sessions, in milliseconds. /// - /// The group. - GroupInfo GetGroup(); + /// The default ping value used for sessions, in milliseconds. + long DefaultPing { get; } + + /// + /// Gets the group identifier. + /// + /// The group identifier. + Guid GroupId { get; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + long PositionTicks { get; set; } + + /// + /// Gets or sets the last activity. + /// + /// The last activity. + DateTime LastActivity { get; set; } + + /// + /// Gets the play queue. + /// + /// The play queue. + PlayQueueManager PlayQueue { get; } /// /// Sets a new state. @@ -30,7 +54,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The filtering type. /// The message to send. /// The cancellation token. - /// The task. + /// The task. Task SendGroupUpdate(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate message, CancellationToken cancellationToken); /// @@ -40,14 +64,14 @@ namespace MediaBrowser.Controller.SyncPlay /// The filtering type. /// The message to send. /// The cancellation token. - /// The task. + /// The task. Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken cancellationToken); /// /// Builds a new playback command with some default values. /// /// The command type. - /// The SendCommand. + /// The SendCommand. SendCommand NewSyncPlayCommand(SendCommandType type); /// @@ -55,21 +79,135 @@ namespace MediaBrowser.Controller.SyncPlay /// /// The update type. /// The data to send. - /// The GroupUpdate. + /// The GroupUpdate. GroupUpdate NewSyncPlayGroupUpdate(GroupUpdateType type, T data); /// /// Converts DateTime to UTC string. /// - /// The date to convert. - /// The UTC string. - string DateToUTCString(DateTime date); + /// The date to convert. + /// The UTC string. + string DateToUTCString(DateTime dateTime); /// /// Sanitizes the PositionTicks, considers the current playing item when available. /// /// The PositionTicks. - /// The sanitized PositionTicks. + /// The sanitized PositionTicks. long SanitizePositionTicks(long? positionTicks); + + /// + /// Updates the ping of a session, in milliseconds. + /// + /// The session. + /// The ping, in milliseconds. + void UpdatePing(SessionInfo session, long ping); + + /// + /// Gets the highest ping in the group, in milliseconds. + /// + /// The highest ping in the group. + long GetHighestPing(); + + /// + /// Sets the session's buffering state. + /// + /// The session. + /// The state. + void SetBuffering(SessionInfo session, bool isBuffering); + + /// + /// Sets the buffering state of all the sessions. + /// + /// The state. + void SetAllBuffering(bool isBuffering); + + /// + /// Gets the group buffering state. + /// + /// true if there is a session buffering in the group; false otherwise. + bool IsBuffering(); + + /// + /// Sets the session's group wait state. + /// + /// The session. + /// The state. + void SetIgnoreGroupWait(SessionInfo session, bool ignoreGroupWait); + + /// + /// Sets a new play queue. + /// + /// The new play queue. + /// The playing item position in the play queue. + /// The start position ticks. + /// true if the play queue has been changed; false is something went wrong. + bool SetPlayQueue(Guid[] playQueue, int playingItemPosition, long startPositionTicks); + + /// + /// Sets the playing item. + /// + /// The new playing item id. + /// true if the play queue has been changed; false is something went wrong. + bool SetPlayingItem(string playlistItemId); + + /// + /// Removes items from the play queue. + /// + /// The items to remove. + /// true if playing item got removed; false otherwise. + bool RemoveFromPlayQueue(string[] playlistItemIds); + + /// + /// Moves an item in the play queue. + /// + /// The playlist id of the item to move. + /// The new position. + /// true if item has been moved; false is something went wrong. + bool MoveItemInPlayQueue(string playlistItemId, int newIndex); + + /// + /// Updates the play queue. + /// + /// The new items to add to the play queue. + /// The mode with which the items will be added. + /// true if the play queue has been changed; false is something went wrong. + bool AddToPlayQueue(Guid[] newItems, string mode); + + /// + /// Restarts current item in play queue. + /// + void RestartCurrentItem(); + + /// + /// Picks next item in play queue. + /// + /// true if the item changed; false otherwise. + bool NextItemInQueue(); + + /// + /// Picks previous item in play queue. + /// + /// true if the item changed; false otherwise. + bool PreviousItemInQueue(); + + /// + /// Sets the repeat mode. + /// + /// The new mode. + void SetRepeatMode(string mode); + + /// + /// Sets the shuffle mode. + /// + /// The new mode. + void SetShuffleMode(string mode); + + /// + /// Creates a play queue update. + /// + /// The reason for the update. + /// The play queue update. + PlayQueueUpdate GetPlayQueueUpdate(PlayQueueUpdateReason reason); } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs index 21dae8e4e6..0815dd79b2 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs @@ -23,21 +23,27 @@ namespace MediaBrowser.Controller.SyncPlay public long PositionTicks { get; set; } /// - /// Gets or sets the playing item id. + /// Gets or sets the client playback status. /// - /// The playing item id. - public Guid PlayingItemId { get; set; } + /// The client playback status. + public bool IsPlaying { get; set; } + + /// + /// Gets or sets the playlist item id of the playing item. + /// + /// The playlist item id. + public string PlaylistItemId { get; set; } /// - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Buffer; } /// - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs new file mode 100644 index 0000000000..5466cbe2f7 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class IgnoreWaitGroupRequest. + /// + public class IgnoreWaitGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the client group-wait status. + /// + /// The client group-wait status. + public bool IgnoreWait { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.IgnoreWait; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs new file mode 100644 index 0000000000..7a293c02fd --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs @@ -0,0 +1,36 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class MovePlaylistItemGroupRequest. + /// + public class MovePlaylistItemGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the playlist id of the item. + /// + /// The playlist id of the item. + public string PlaylistItemId { get; set; } + + /// + /// Gets or sets the new position. + /// + /// The new position. + public int NewIndex { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Queue; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs new file mode 100644 index 0000000000..d19df2c6a0 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class NextTrackGroupRequest. + /// + public class NextTrackGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlaylistItemId { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.NextTrack; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs index 21a46add8c..facb25155c 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs @@ -10,15 +10,15 @@ namespace MediaBrowser.Controller.SyncPlay public class PauseGroupRequest : IPlaybackGroupRequest { /// - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Pause; } /// - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs index 2f78edfc56..631bf245b1 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs @@ -2,7 +2,6 @@ using System.Threading; using MediaBrowser.Model.SyncPlay; using MediaBrowser.Controller.Session; -// FIXME: not really group related, can be moved up to SyncPlayController maybe? namespace MediaBrowser.Controller.SyncPlay { /// @@ -17,15 +16,15 @@ namespace MediaBrowser.Controller.SyncPlay public long Ping { get; set; } /// - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Ping; } /// - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs index 942229a775..f3dd769e46 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using MediaBrowser.Model.SyncPlay; using MediaBrowser.Controller.Session; @@ -9,16 +10,34 @@ namespace MediaBrowser.Controller.SyncPlay /// public class PlayGroupRequest : IPlaybackGroupRequest { + /// + /// Gets or sets the playing queue. + /// + /// The playing queue. + public Guid[] PlayingQueue { get; set; } + + /// + /// Gets or sets the playing item from the queue. + /// + /// The playing item. + public int PlayingItemPosition { get; set; } + + /// + /// Gets or sets the start position ticks. + /// + /// The start position ticks. + public long StartPositionTicks { get; set; } + /// - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Play; } /// - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs new file mode 100644 index 0000000000..663011b429 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class PreviousTrackGroupRequest. + /// + public class PreviousTrackGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlaylistItemId { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.PreviousTrack; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs new file mode 100644 index 0000000000..01c08cc860 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class QueueGroupRequest. + /// + public class QueueGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the items to queue. + /// + /// The items to queue. + public Guid[] ItemIds { get; set; } + + /// + /// Gets or sets the mode in which to add the new items. + /// + /// The mode. + public string Mode { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Queue; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs index ee88ddddbb..16bc67c617 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs @@ -23,21 +23,27 @@ namespace MediaBrowser.Controller.SyncPlay public long PositionTicks { get; set; } /// - /// Gets or sets the playing item id. + /// Gets or sets the client playback status. /// - /// The playing item id. - public Guid PlayingItemId { get; set; } + /// The client playback status. + public bool IsPlaying { get; set; } + + /// + /// Gets or sets the playlist item id of the playing item. + /// + /// The playlist item id. + public string PlaylistItemId { get; set; } /// - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Ready; } /// - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs new file mode 100644 index 0000000000..3fc77f6771 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class RemoveFromPlaylistGroupRequest. + /// + public class RemoveFromPlaylistGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the playlist ids ot the items. + /// + /// The playlist ids ot the items. + public string[] PlaylistItemIds { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Queue; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs index bb5e7a343e..24d9be5073 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs @@ -16,15 +16,15 @@ namespace MediaBrowser.Controller.SyncPlay public long PositionTicks { get; set; } /// - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Seek; } /// - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs new file mode 100644 index 0000000000..d70559899a --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class SetPlaylistItemGroupRequest. + /// + public class SetPlaylistItemGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the playlist id of the playing item. + /// + /// The playlist id of the playing item. + public string PlaylistItemId { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.SetPlaylistItem; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs new file mode 100644 index 0000000000..5f36f60e45 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class SetRepeatModeGroupRequest. + /// + public class SetRepeatModeGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the repeat mode. + /// + /// The repeat mode. + public string Mode { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.SetRepeatMode; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs new file mode 100644 index 0000000000..472455fd3b --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class SetShuffleModeGroupRequest. + /// + public class SetShuffleModeGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the shuffle mode. + /// + /// The shuffle mode. + public string Mode { get; set; } + + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.SetShuffleMode; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs new file mode 100644 index 0000000000..f1581c98d9 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs @@ -0,0 +1,24 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class StopGroupRequest. + /// + public class StopGroupRequest : IPlaybackGroupRequest + { + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Stop; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs new file mode 100644 index 0000000000..1072952081 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs @@ -0,0 +1,24 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class UnpauseGroupRequest. + /// + public class UnpauseGroupRequest : IPlaybackGroupRequest + { + /// + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Unpause; + } + + /// + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs new file mode 100644 index 0000000000..701982cc00 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs @@ -0,0 +1,596 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.SyncPlay; + +namespace MediaBrowser.Controller.SyncPlay +{ + static class ListShuffleExtension + { + private static Random rng = new Random(); + public static void Shuffle(this IList 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; + } + } + } + + /// + /// Class PlayQueueManager. + /// + public class PlayQueueManager : IDisposable + { + /// + /// Gets or sets the playing item index. + /// + /// The playing item index. + public int PlayingItemIndex { get; private set; } + + /// + /// Gets or sets the last time the queue has been changed. + /// + /// The last time the queue has been changed. + public DateTime LastChange { get; private set; } + + /// + /// Gets the sorted playlist. + /// + /// The sorted playlist, or play queue of the group. + private List SortedPlaylist { get; set; } = new List(); + + /// + /// Gets the shuffled playlist. + /// + /// The shuffled playlist, or play queue of the group. + private List ShuffledPlaylist { get; set; } = new List(); + + /// + /// Gets or sets the shuffle mode. + /// + /// The shuffle mode. + public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted; + + /// + /// Gets or sets the repeat mode. + /// + /// The repeat mode. + public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone; + + /// + /// Gets or sets the progressive id counter. + /// + /// The progressive id. + private int ProgressiveId { get; set; } = 0; + + private bool _disposed = false; + + /// + /// Initializes a new instance of the class. + /// + public PlayQueueManager() + { + Reset(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + /// + /// Gets the next available id. + /// + /// The next available id. + private int GetNextProgressiveId() { + return ProgressiveId++; + } + + /// + /// Creates a list from the array of items. Each item is given an unique playlist id. + /// + /// The list of queue items. + private List CreateQueueItemsFromArray(Guid[] items) + { + return items.ToList() + .Select(item => new QueueItem() + { + ItemId = item, + PlaylistItemId = "syncPlayItem" + GetNextProgressiveId() + }) + .ToList(); + } + + /// + /// Gets the current playlist, depending on the shuffle mode. + /// + /// The playlist. + private List GetPlaylistAsList() + { + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + return ShuffledPlaylist; + } + else + { + return SortedPlaylist; + } + } + + /// + /// Gets the current playlist as an array, depending on the shuffle mode. + /// + /// The array of items in the playlist. + public QueueItem[] GetPlaylist() { + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + return ShuffledPlaylist.ToArray(); + } + else + { + return SortedPlaylist.ToArray(); + } + } + + /// + /// Sets a new playlist. Playing item is set to none. Resets shuffle mode and repeat mode as well. + /// + /// The new items of the playlist. + public void SetPlaylist(Guid[] items) + { + SortedPlaylist = CreateQueueItemsFromArray(items); + PlayingItemIndex = -1; + ShuffleMode = GroupShuffleMode.Sorted; + RepeatMode = GroupRepeatMode.RepeatNone; + LastChange = DateTime.UtcNow; + } + + /// + /// Appends new items to the playlist. The specified order is mantained for the sorted playlist, whereas items get shuffled for the shuffled playlist. + /// + /// The items to add to the playlist. + public void Queue(Guid[] items) + { + var newItems = CreateQueueItemsFromArray(items); + SortedPlaylist.AddRange(newItems); + + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + newItems.Shuffle(); + ShuffledPlaylist.AddRange(newItems); + } + + LastChange = DateTime.UtcNow; + } + + /// + /// Shuffles the playlist. Shuffle mode is changed. + /// + public void ShufflePlaylist() + { + if (SortedPlaylist.Count() == 0) + { + return; + } + + if (PlayingItemIndex < 0) { + ShuffledPlaylist = SortedPlaylist.ToList(); + ShuffledPlaylist.Shuffle(); + } + else + { + var playingItem = SortedPlaylist[PlayingItemIndex]; + ShuffledPlaylist = SortedPlaylist.ToList(); + ShuffledPlaylist.RemoveAt(PlayingItemIndex); + ShuffledPlaylist.Shuffle(); + ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList(); + PlayingItemIndex = 0; + } + + ShuffleMode = GroupShuffleMode.Shuffle; + LastChange = DateTime.UtcNow; + } + + /// + /// Resets the playlist to sorted mode. Shuffle mode is changed. + /// + public void SortShuffledPlaylist() + { + if (PlayingItemIndex >= 0) + { + var playingItem = ShuffledPlaylist[PlayingItemIndex]; + PlayingItemIndex = SortedPlaylist.IndexOf(playingItem); + } + + ShuffledPlaylist.Clear(); + + ShuffleMode = GroupShuffleMode.Sorted; + LastChange = DateTime.UtcNow; + } + + /// + /// Clears the playlist. + /// + /// Whether to remove the playing item as well. + public void ClearPlaylist(bool clearPlayingItem) + { + var playingItem = SortedPlaylist[PlayingItemIndex]; + SortedPlaylist.Clear(); + ShuffledPlaylist.Clear(); + LastChange = DateTime.UtcNow; + + if (!clearPlayingItem && playingItem != null) + { + SortedPlaylist.Add(playingItem); + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + SortedPlaylist.Add(playingItem); + } + } + } + + /// + /// Adds new items to the playlist right after the playing item. The specified order is mantained for the sorted playlist, whereas items get shuffled for the shuffled playlist. + /// + /// The items to add to the playlist. + public void QueueNext(Guid[] items) + { + var newItems = CreateQueueItemsFromArray(items); + + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + // Append items to sorted playlist as they are + SortedPlaylist.AddRange(newItems); + // Shuffle items before adding to shuffled playlist + newItems.Shuffle(); + ShuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems); + } + else + { + SortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems); + } + + LastChange = DateTime.UtcNow; + } + + /// + /// Gets playlist id of the playing item, if any. + /// + /// The playlist id of the playing item. + public string GetPlayingItemPlaylistId() + { + if (PlayingItemIndex < 0) + { + return null; + } + + var list = GetPlaylistAsList(); + + if (list.Count() > 0) + { + return list[PlayingItemIndex].PlaylistItemId; + } + else + { + return null; + } + } + + /// + /// Gets the playing item id, if any. + /// + /// The playing item id. + public Guid GetPlayingItemId() + { + if (PlayingItemIndex < 0) + { + return Guid.Empty; + } + + var list = GetPlaylistAsList(); + + if (list.Count() > 0) + { + return list[PlayingItemIndex].ItemId; + } + else + { + return Guid.Empty; + } + } + + /// + /// Sets the playing item using its id. If not in the playlist, the playing item is reset. + /// + /// The new playing item id. + public void SetPlayingItemById(Guid itemId) + { + var itemIds = GetPlaylistAsList().Select(queueItem => queueItem.ItemId).ToList(); + PlayingItemIndex = itemIds.IndexOf(itemId); + LastChange = DateTime.UtcNow; + } + + /// + /// Sets the playing item using its playlist id. If not in the playlist, the playing item is reset. + /// + /// The new playing item id. + /// true if playing item has been set; false if item is not in the playlist. + public bool SetPlayingItemByPlaylistId(string playlistItemId) + { + var playlistIds = GetPlaylistAsList().Select(queueItem => queueItem.PlaylistItemId).ToList(); + PlayingItemIndex = playlistIds.IndexOf(playlistItemId); + LastChange = DateTime.UtcNow; + return PlayingItemIndex != -1; + } + + /// + /// Sets the playing item using its position. If not in range, the playing item is reset. + /// + /// The new playing item index. + public void SetPlayingItemByIndex(int playlistIndex) + { + var list = GetPlaylistAsList(); + if (playlistIndex < 0 || playlistIndex > list.Count()) + { + PlayingItemIndex = -1; + } + else + { + PlayingItemIndex = playlistIndex; + } + + LastChange = DateTime.UtcNow; + } + + /// + /// Removes items from the playlist. If not removed, the playing item is preserved. + /// + /// The items to remove. + /// true if playing item got removed; false otherwise. + public bool RemoveFromPlaylist(string[] playlistItemIds) + { + var playingItem = SortedPlaylist[PlayingItemIndex]; + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + playingItem = ShuffledPlaylist[PlayingItemIndex]; + } + + var playlistItemIdsList = playlistItemIds.ToList(); + SortedPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId)); + ShuffledPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId)); + + LastChange = DateTime.UtcNow; + + if (playingItem != null) + { + if (playlistItemIds.Contains(playingItem.PlaylistItemId)) + { + // Playing item has been removed, picking previous item + PlayingItemIndex--; + if (PlayingItemIndex < 0) + { + // Was first element, picking next if available + PlayingItemIndex = SortedPlaylist.Count() > 0 ? 0 : -1; + } + + return true; + } + else + { + // Restoring playing item + SetPlayingItemByPlaylistId(playingItem.PlaylistItemId); + return false; + } + } + else + { + return false; + } + } + + /// + /// Moves an item in the playlist to another position. + /// + /// The item to move. + /// The new position. + /// true if the item has been moved; false otherwise. + public bool MovePlaylistItem(string playlistItemId, int newIndex) + { + var list = GetPlaylistAsList(); + var playingItem = list[PlayingItemIndex]; + + var playlistIds = list.Select(queueItem => queueItem.PlaylistItemId).ToList(); + var oldIndex = playlistIds.IndexOf(playlistItemId); + if (oldIndex < 0) { + return false; + } + + var queueItem = list[oldIndex]; + list.RemoveAt(oldIndex); + newIndex = newIndex > list.Count() ? list.Count() : newIndex; + newIndex = newIndex < 0 ? 0 : newIndex; + list.Insert(newIndex, queueItem); + + LastChange = DateTime.UtcNow; + PlayingItemIndex = list.IndexOf(playingItem); + return true; + } + + /// + /// Resets the playlist to its initial state. + /// + public void Reset() + { + ProgressiveId = 0; + SortedPlaylist.Clear(); + ShuffledPlaylist.Clear(); + PlayingItemIndex = -1; + ShuffleMode = GroupShuffleMode.Sorted; + RepeatMode = GroupRepeatMode.RepeatNone; + LastChange = DateTime.UtcNow; + } + + /// + /// Sets the repeat mode. + /// + /// The new mode. + public void SetRepeatMode(string mode) + { + switch (mode) + { + case "RepeatOne": + RepeatMode = GroupRepeatMode.RepeatOne; + break; + case "RepeatAll": + RepeatMode = GroupRepeatMode.RepeatAll; + break; + default: + RepeatMode = GroupRepeatMode.RepeatNone; + break; + } + + LastChange = DateTime.UtcNow; + } + + /// + /// Sets the shuffle mode. + /// + /// The new mode. + public void SetShuffleMode(string mode) + { + switch (mode) + { + case "Shuffle": + ShufflePlaylist(); + break; + default: + SortShuffledPlaylist(); + break; + } + } + + /// + /// Toggles the shuffle mode between sorted and shuffled. + /// + public void ToggleShuffleMode() + { + SetShuffleMode(ShuffleMode.Equals(GroupShuffleMode.Shuffle) ? "Shuffle" : ""); + } + + /// + /// Gets the next item in the playlist considering repeat mode and shuffle mode. + /// + /// The next item in the playlist. + public QueueItem GetNextItemPlaylistId() + { + int newIndex; + var playlist = GetPlaylistAsList(); + + switch (RepeatMode) + { + case GroupRepeatMode.RepeatOne: + newIndex = PlayingItemIndex; + break; + case GroupRepeatMode.RepeatAll: + newIndex = PlayingItemIndex + 1; + if (newIndex >= playlist.Count()) + { + newIndex = 0; + } + break; + default: + newIndex = PlayingItemIndex + 1; + break; + } + + if (newIndex < 0 || newIndex >= playlist.Count()) + { + return null; + } + + return playlist[newIndex]; + } + + /// + /// Sets the next item in the queue as playing item. + /// + /// true if the playing item changed; false otherwise. + public bool Next() + { + if (RepeatMode.Equals(GroupRepeatMode.RepeatOne)) + { + LastChange = DateTime.UtcNow; + return true; + } + + PlayingItemIndex++; + if (PlayingItemIndex >= SortedPlaylist.Count()) + { + if (RepeatMode.Equals(GroupRepeatMode.RepeatAll)) + { + PlayingItemIndex = 0; + } + else + { + PlayingItemIndex--; + return false; + } + } + + LastChange = DateTime.UtcNow; + return true; + } + + /// + /// Sets the previous item in the queue as playing item. + /// + /// true if the playing item changed; false otherwise. + public bool Previous() + { + if (RepeatMode.Equals(GroupRepeatMode.RepeatOne)) + { + LastChange = DateTime.UtcNow; + return true; + } + + PlayingItemIndex--; + if (PlayingItemIndex < 0) + { + if (RepeatMode.Equals(GroupRepeatMode.RepeatAll)) + { + PlayingItemIndex = SortedPlaylist.Count() - 1; + } + else + { + PlayingItemIndex++; + return false; + } + } + + LastChange = DateTime.UtcNow; + return true; + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs b/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs deleted file mode 100644 index 0b72d16686..0000000000 --- a/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Threading; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.SyncPlay; - -namespace MediaBrowser.Controller.SyncPlay -{ - /// - /// Class SyncPlayAbstractState. - /// - /// - /// Class is not thread-safe, external locking is required when accessing methods. - /// - public abstract class SyncPlayAbstractState : ISyncPlayState - { - /// - public abstract GroupState GetGroupState(); - - /// - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - GroupInfo group = context.GetGroup(); - - // Collected pings are used to account for network latency when unpausing playback - group.UpdatePing(session, request.Ping); - - return true; - } - } -} -- cgit v1.2.3