aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/SyncPlay
diff options
context:
space:
mode:
authorIonut Andrei Oanca <oancaionutandrei@gmail.com>2020-11-13 15:13:32 +0100
committerIonut Andrei Oanca <oancaionutandrei@gmail.com>2020-11-14 12:33:54 +0100
commit1dbc91978ece81628c339d1dc3b53f6d250cb005 (patch)
treea4a8a90f3347fbcf44a8e00a85329828b08cc746 /MediaBrowser.Controller/SyncPlay
parent563a6fb3c7cd933059995cac710f46287774d401 (diff)
Address requested changes and fix some warnings
Diffstat (limited to 'MediaBrowser.Controller/SyncPlay')
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs216
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs126
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs165
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs168
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs655
-rw-r--r--MediaBrowser.Controller/SyncPlay/IGroupController.cs (renamed from MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs)9
-rw-r--r--MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs27
-rw-r--r--MediaBrowser.Controller/SyncPlay/IGroupState.cs (renamed from MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs)52
-rw-r--r--MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs (renamed from MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs)17
-rw-r--r--MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs23
-rw-r--r--MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs6
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs30
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs24
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs30
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs24
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs24
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs)15
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs27
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs)13
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextTrackGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs)13
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs21
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs)13
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs)18
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousTrackGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs)13
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs)18
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs)15
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs28
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs)13
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs)13
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs)13
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs (renamed from MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs)13
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/StopGroupRequest.cs21
-rw-r--r--MediaBrowser.Controller/SyncPlay/PlaybackRequests/UnpauseGroupRequest.cs21
-rw-r--r--MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs315
34 files changed, 1766 insertions, 433 deletions
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs
new file mode 100644
index 000000000..829ef2bba
--- /dev/null
+++ b/MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+ /// <summary>
+ /// Class AbstractGroupState.
+ /// </summary>
+ /// <remarks>
+ /// Class is not thread-safe, external locking is required when accessing methods.
+ /// </remarks>
+ public abstract class AbstractGroupState : IGroupState
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AbstractGroupState"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+ protected AbstractGroupState(ILogger logger)
+ {
+ Logger = logger;
+ }
+
+ /// <inheritdoc />
+ public abstract GroupStateType Type { get; }
+
+ /// <summary>
+ /// Gets the logger.
+ /// </summary>
+ protected ILogger Logger { get; }
+
+ /// <inheritdoc />
+ public abstract void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken);
+
+ /// <inheritdoc />
+ public abstract void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken);
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, IGroupPlaybackRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ var waitingState = new WaitingGroupState(Logger);
+ context.SetState(waitingState);
+ waitingState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, RemoveFromPlaylistGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ var playingItemRemoved = context.RemoveFromPlayQueue(request.PlaylistItemIds);
+
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RemoveItems);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+
+ if (playingItemRemoved && !context.PlayQueue.IsItemPlaying())
+ {
+ 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 />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, MovePlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ var result = context.MoveItemInPlayQueue(request.PlaylistItemId, request.NewIndex);
+
+ if (!result)
+ {
+ Logger.LogError("HandleRequest: {0} in group {1}, unable to move item in play queue.", request.Type, context.GroupId.ToString());
+ return;
+ }
+
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.MoveItem);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, QueueGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ var result = context.AddToPlayQueue(request.ItemIds, request.Mode);
+
+ if (!result)
+ {
+ Logger.LogError("HandleRequest: {0} in group {1}, unable to add items to play queue.", request.Type, context.GroupId.ToString());
+ return;
+ }
+
+ var reason = request.Mode.Equals("next", StringComparison.OrdinalIgnoreCase) ? PlayQueueUpdateReason.QueueNext : PlayQueueUpdateReason.Queue;
+ var playQueueUpdate = context.GetPlayQueueUpdate(reason);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ UnhandledRequest(request);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetRepeatModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ context.SetRepeatMode(request.Mode);
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RepeatMode);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetShuffleModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ context.SetShuffleMode(request.Mode);
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.ShuffleMode);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ 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.
+ context.UpdatePing(session, request.Ping);
+ }
+
+ /// <inheritdoc />
+ public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ context.SetIgnoreGroupWait(session, request.IgnoreWait);
+ }
+
+ /// <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.Type, Type);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/IdleGroupState.cs
new file mode 100644
index 000000000..1a507e044
--- /dev/null
+++ b/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);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs
new file mode 100644
index 000000000..11f526d31
--- /dev/null
+++ b/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+ /// <summary>
+ /// Class PausedGroupState.
+ /// </summary>
+ /// <remarks>
+ /// Class is not thread-safe, external locking is required when accessing methods.
+ /// </remarks>
+ public class PausedGroupState : AbstractGroupState
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PausedGroupState"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+ public PausedGroupState(ILogger logger)
+ : base(logger)
+ {
+ // Do nothing.
+ }
+
+ /// <inheritdoc />
+ public override GroupStateType Type
+ {
+ get
+ {
+ return GroupStateType.Paused;
+ }
+ }
+
+ /// <inheritdoc />
+ public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Wait for session to be ready.
+ var waitingState = new WaitingGroupState(Logger);
+ context.SetState(waitingState);
+ waitingState.SessionJoined(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 playingState = new PlayingGroupState(Logger);
+ context.SetState(playingState);
+ playingState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ if (!prevState.Equals(Type))
+ {
+ // Pause group and compute the media playback position.
+ var currentTime = DateTime.UtcNow;
+ var elapsedTime = currentTime - context.LastActivity;
+ context.LastActivity = currentTime;
+ // Elapsed time is negative if event happens
+ // during the delay added to account for latency.
+ // In this phase clients haven't started the playback yet.
+ // In other words, LastActivity is in the future,
+ // when playback unpause is supposed to happen.
+ // Seek only if playback actually started.
+ context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
+
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, session, cancellationToken);
+ }
+ else
+ {
+ // Client got lost, sending current state.
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+ }
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Change state.
+ var idleState = new IdleGroupState(Logger);
+ context.SetState(idleState);
+ idleState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest 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, BufferGroupRequest 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, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ if (prevState.Equals(Type))
+ {
+ // Client got lost, sending current state.
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+ }
+ else if (prevState.Equals(GroupStateType.Waiting))
+ {
+ // Sending current state to all clients.
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, 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);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs
new file mode 100644
index 000000000..2aa759811
--- /dev/null
+++ b/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+ /// <summary>
+ /// Class PlayingGroupState.
+ /// </summary>
+ /// <remarks>
+ /// Class is not thread-safe, external locking is required when accessing methods.
+ /// </remarks>
+ public class PlayingGroupState : AbstractGroupState
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlayingGroupState"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+ public PlayingGroupState(ILogger logger)
+ : base(logger)
+ {
+ // Do nothing.
+ }
+
+ /// <inheritdoc />
+ public override GroupStateType Type
+ {
+ 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 />
+ public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Wait for session to be ready.
+ var waitingState = new WaitingGroupState(Logger);
+ context.SetState(waitingState);
+ waitingState.SessionJoined(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)
+ {
+ if (!prevState.Equals(Type))
+ {
+ // Pick a suitable time that accounts for latency.
+ var delayMillis = Math.Max(context.GetHighestPing() * 2, context.DefaultPing);
+
+ // Unpause group and set starting point in future.
+ // 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.
+ // Playback synchronization will mainly happen client side.
+ context.LastActivity = DateTime.UtcNow.AddMilliseconds(delayMillis);
+
+ var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
+ context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, session, cancellationToken);
+ }
+ else
+ {
+ // Client got lost, sending current state.
+ var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
+ context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+ }
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Change state.
+ var pausedState = new PausedGroupState(Logger);
+ context.SetState(pausedState);
+ pausedState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Change state.
+ var idleState = new IdleGroupState(Logger);
+ context.SetState(idleState);
+ idleState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest 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, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ if (IgnoreBuffering)
+ {
+ return;
+ }
+
+ // 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, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ if (prevState.Equals(Type))
+ {
+ // Group was not waiting, make sure client has latest state.
+ var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
+ context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+ }
+ else if (prevState.Equals(GroupStateType.Waiting))
+ {
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, 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);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
new file mode 100644
index 000000000..7f454570a
--- /dev/null
+++ b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
@@ -0,0 +1,655 @@
+using System;
+using System.Threading;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Controller.SyncPlay
+{
+ /// <summary>
+ /// Class WaitingGroupState.
+ /// </summary>
+ /// <remarks>
+ /// Class is not thread-safe, external locking is required when accessing methods.
+ /// </remarks>
+ public class WaitingGroupState : AbstractGroupState
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WaitingGroupState"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+ public WaitingGroupState(ILogger logger)
+ : base(logger)
+ {
+ // Do nothing.
+ }
+
+ /// <inheritdoc />
+ public override GroupStateType Type
+ {
+ 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 />
+ public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ if (prevState.Equals(GroupStateType.Playing))
+ {
+ ResumePlaying = true;
+ // Pause group and compute the media playback position.
+ var currentTime = DateTime.UtcNow;
+ var elapsedTime = currentTime - context.LastActivity;
+ context.LastActivity = currentTime;
+ // Elapsed time is negative if event happens
+ // during the delay added to account for latency.
+ // In this phase clients haven't started the playback yet.
+ // In other words, LastActivity is in the future,
+ // when playback unpause is supposed to happen.
+ // Seek only if playback actually started.
+ context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
+ }
+
+ // Prepare new session.
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
+
+ context.SetBuffering(session, true);
+
+ // Send pause command to all non-buffering sessions.
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ context.SetBuffering(session, false);
+
+ if (!context.IsBuffering())
+ {
+ if (ResumePlaying)
+ {
+ // Client, that was buffering, left the group.
+ var playingState = new PlayingGroupState(Logger);
+ context.SetState(playingState);
+ var unpauseRequest = new UnpauseGroupRequest();
+ playingState.HandleRequest(context, Type, unpauseRequest, session, cancellationToken);
+
+ Logger.LogDebug("SessionLeaving: {0} left the group {1}, notifying others to resume.", session.Id, context.GroupId.ToString());
+ }
+ else
+ {
+ // Group is ready, returning to previous state.
+ var pausedState = new PausedGroupState(Logger);
+ context.SetState(pausedState);
+
+ Logger.LogDebug("SessionLeaving: {0} left the group {1}, returning to previous state.", session.Id, context.GroupId.ToString());
+ }
+ }
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ ResumePlaying = true;
+
+ var setQueueStatus = context.SetPlayQueue(request.PlayingQueue, request.PlayingItemPosition, request.StartPositionTicks);
+ if (!setQueueStatus)
+ {
+ Logger.LogError("HandleRequest: {0} in group {1}, unable to set playing queue.", request.Type, context.GroupId.ToString());
+
+ // Ignore request and return to previous state.
+ IGroupState newState = prevState switch {
+ GroupStateType.Playing => new PlayingGroupState(Logger),
+ GroupStateType.Paused => new PausedGroupState(Logger),
+ _ => new IdleGroupState(Logger)
+ };
+
+ context.SetState(newState);
+ return;
+ }
+
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+
+ // Reset status of sessions and await for all Ready events.
+ context.SetAllBuffering(true);
+
+ Logger.LogDebug("HandleRequest: {0} in group {1}, {2} set a new play queue.", request.Type, context.GroupId.ToString(), session.Id);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ ResumePlaying = true;
+
+ var result = context.SetPlayingItem(request.PlaylistItemId);
+ if (result)
+ {
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+
+ // Reset status of sessions and await for all Ready events.
+ context.SetAllBuffering(true);
+ }
+ else
+ {
+ // Return to old state.
+ IGroupState newState = prevState switch
+ {
+ GroupStateType.Playing => new PlayingGroupState(Logger),
+ GroupStateType.Paused => new PausedGroupState(Logger),
+ _ => new IdleGroupState(Logger)
+ };
+
+ context.SetState(newState);
+
+ Logger.LogDebug("HandleRequest: {0} in group {1}, unable to change current playing item.", request.Type, context.GroupId.ToString());
+ }
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ if (prevState.Equals(GroupStateType.Idle))
+ {
+ ResumePlaying = true;
+ context.RestartCurrentItem();
+
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+
+ // Reset status of sessions and await for all Ready events.
+ context.SetAllBuffering(true);
+
+ Logger.LogDebug("HandleRequest: {0} in group {1}, waiting for all ready events.", request.Type, context.GroupId.ToString());
+ }
+ else
+ {
+ if (ResumePlaying)
+ {
+ 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.
+ context.SetAllBuffering(false);
+
+ // Change state.
+ var playingState = new PlayingGroupState(Logger)
+ {
+ IgnoreBuffering = true
+ };
+ context.SetState(playingState);
+ playingState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+ else
+ {
+ // Group would have gone to paused state, now will go to playing state when ready.
+ ResumePlaying = true;
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, session, cancellationToken);
+ }
+ }
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ // Wait for sessions to be ready, then switch to paused state.
+ ResumePlaying = false;
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ // Change state.
+ var idleState = new IdleGroupState(Logger);
+ context.SetState(idleState);
+ idleState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ if (prevState.Equals(GroupStateType.Playing))
+ {
+ ResumePlaying = true;
+ }
+ else if (prevState.Equals(GroupStateType.Paused))
+ {
+ ResumePlaying = false;
+ }
+
+ // Sanitize PositionTicks.
+ var ticks = context.SanitizePositionTicks(request.PositionTicks);
+
+ // Seek.
+ context.PositionTicks = ticks;
+ context.LastActivity = DateTime.UtcNow;
+
+ var command = context.NewSyncPlayCommand(SendCommandType.Seek);
+ context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
+
+ // Reset status of sessions and await for all Ready events.
+ context.SetAllBuffering(true);
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ // Make sure the client is playing the correct item.
+ if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase))
+ {
+ 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 updateSession = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
+ context.SetBuffering(session, true);
+
+ return;
+ }
+
+ if (prevState.Equals(GroupStateType.Playing))
+ {
+ // Resume playback when all ready.
+ ResumePlaying = true;
+
+ context.SetBuffering(session, true);
+
+ // Pause group and compute the media playback position.
+ var currentTime = DateTime.UtcNow;
+ var elapsedTime = currentTime - context.LastActivity;
+ context.LastActivity = currentTime;
+ // Elapsed time is negative if event happens
+ // during the delay added to account for latency.
+ // In this phase clients haven't started the playback yet.
+ // In other words, LastActivity is in the future,
+ // when playback unpause is supposed to happen.
+ // Seek only if playback actually started.
+ context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
+
+ // Send pause command to all non-buffering sessions.
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
+ }
+ else if (prevState.Equals(GroupStateType.Paused))
+ {
+ // Don't resume playback when all ready.
+ ResumePlaying = false;
+
+ context.SetBuffering(session, true);
+
+ // Send pause command to buffering session.
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+ }
+ else if (prevState.Equals(GroupStateType.Waiting))
+ {
+ // Another session is now buffering.
+ context.SetBuffering(session, true);
+
+ if (!ResumePlaying)
+ {
+ // Force update for this session that should be paused.
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+ }
+ }
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, session, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ // Make sure the client is playing the correct item.
+ if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase))
+ {
+ 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 update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
+ context.SetBuffering(session, true);
+
+ return;
+ }
+
+ // Compute elapsed time between the client reported time and now.
+ // Elapsed time is used to estimate the client position when playback is unpaused.
+ // Ideally, the request is received and handled without major delays.
+ // However, to avoid waiting indefinitely when a client is not reporting a correct time,
+ // the elapsed time is ignored after a certain threshold.
+ var currentTime = DateTime.UtcNow;
+ var elapsedTime = currentTime.Subtract(request.When);
+ var timeSyncThresholdTicks = TimeSpan.FromMilliseconds(context.TimeSyncOffset).Ticks;
+ if (Math.Abs(elapsedTime.Ticks) > timeSyncThresholdTicks)
+ {
+ 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;
+ }
+
+ // Ignore elapsed time if client is paused.
+ if (!request.IsPlaying)
+ {
+ elapsedTime = TimeSpan.Zero;
+ }
+
+ var requestTicks = context.SanitizePositionTicks(request.PositionTicks);
+ var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime;
+ var delayTicks = context.PositionTicks - clientPosition.Ticks;
+ var maxPlaybackOffsetTicks = TimeSpan.FromMilliseconds(context.MaxPlaybackOffset).Ticks;
+
+ 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)
+ {
+ // Handle case where session reported as ready but in reality
+ // it has no clue of the real position nor the playback state.
+ if (!request.IsPlaying && Math.Abs(delayTicks) > maxPlaybackOffsetTicks)
+ {
+ // Session not ready at all.
+ context.SetBuffering(session, true);
+
+ // Correcting session's position.
+ var command = context.NewSyncPlayCommand(SendCommandType.Seek);
+ context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, session, cancellationToken);
+
+ Logger.LogWarning("HandleRequest: {0} in group {1}, {2} got lost in time, correcting.", request.Type, context.GroupId.ToString(), session.Id);
+ return;
+ }
+
+ // Session is ready.
+ context.SetBuffering(session, false);
+
+ if (context.IsBuffering())
+ {
+ // Others are still buffering, tell this client to pause when ready.
+ var command = context.NewSyncPlayCommand(SendCommandType.Pause);
+ var pauseAtTime = currentTime.AddTicks(delayTicks);
+ command.When = context.DateToUTCString(pauseAtTime);
+ 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.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds);
+ }
+ else
+ {
+ // If all ready, then start playback.
+ // Let other clients resume as soon as the buffering client catches up.
+ if (delayTicks > context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond)
+ {
+ // Client that was buffering is recovering, notifying others to resume.
+ context.LastActivity = currentTime.AddTicks(delayTicks);
+ var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
+ var filter = SyncPlayBroadcastType.AllExceptCurrentSession;
+ if (!request.IsPlaying)
+ {
+ filter = SyncPlayBroadcastType.AllGroup;
+ }
+
+ context.SendCommand(session, filter, command, cancellationToken);
+
+ 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
+ {
+ // Client, that was buffering, resumed playback but did not update others in time.
+ delayTicks = context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond;
+ delayTicks = Math.Max(delayTicks, context.DefaultPing);
+
+ context.LastActivity = currentTime.AddTicks(delayTicks);
+
+ var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
+ 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.Type, context.GroupId.ToString(), session.Id, TimeSpan.FromTicks(delayTicks).TotalSeconds);
+ }
+
+ // Change state.
+ var playingState = new PlayingGroupState(Logger);
+ context.SetState(playingState);
+ playingState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+ }
+ else
+ {
+ // Check that session is really ready, tollerate player imperfections under a certain threshold.
+ if (Math.Abs(context.PositionTicks - requestTicks) > maxPlaybackOffsetTicks)
+ {
+ // Session still not ready.
+ context.SetBuffering(session, true);
+ // Session is seeking to wrong position, correcting.
+ var command = context.NewSyncPlayCommand(SendCommandType.Seek);
+ context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
+
+ // Notify relevant state change event.
+ SendGroupStateUpdate(context, request, session, cancellationToken);
+
+ Logger.LogWarning("HandleRequest: {0} in group {1}, {2} was seeking to wrong position, correcting.", request.Type, context.GroupId.ToString(), session.Id);
+ return;
+ }
+ else
+ {
+ // Session is ready.
+ context.SetBuffering(session, false);
+ }
+
+ if (!context.IsBuffering())
+ {
+ // Group is ready, returning to previous state.
+ var pausedState = new PausedGroupState(Logger);
+ context.SetState(pausedState);
+
+ if (InitialState.Equals(GroupStateType.Playing))
+ {
+ // Group went from playing to waiting state and a pause request occured while waiting.
+ var pauserequest = new PauseGroupRequest();
+ pausedState.HandleRequest(context, Type, pauserequest, session, cancellationToken);
+ }
+ else if (InitialState.Equals(GroupStateType.Paused))
+ {
+ pausedState.HandleRequest(context, Type, request, session, cancellationToken);
+ }
+
+ Logger.LogDebug("HandleRequest: {0} in group {1}, {2} is ready, returning to previous state.", request.Type, context.GroupId.ToString(), session.Id);
+ }
+ }
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ ResumePlaying = true;
+
+ // Make sure the client knows the playing item, to avoid duplicate requests.
+ if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.Type, context.GroupId.ToString());
+ return;
+ }
+
+ var newItem = context.NextItemInQueue();
+ if (newItem)
+ {
+ // Send playing-queue update.
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NextTrack);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+
+ // Reset status of sessions and await for all Ready events.
+ context.SetAllBuffering(true);
+ }
+ else
+ {
+ // Return to old state.
+ IGroupState newState = prevState switch
+ {
+ GroupStateType.Playing => new PlayingGroupState(Logger),
+ GroupStateType.Paused => new PausedGroupState(Logger),
+ _ => new IdleGroupState(Logger)
+ };
+
+ context.SetState(newState);
+
+ Logger.LogDebug("HandleRequest: {0} in group {1}, no next track available.", request.Type, context.GroupId.ToString());
+ }
+ }
+
+ /// <inheritdoc />
+ public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
+ {
+ // Save state if first event.
+ if (!InitialStateSet)
+ {
+ InitialState = prevState;
+ InitialStateSet = true;
+ }
+
+ ResumePlaying = true;
+
+ // Make sure the client knows the playing item, to avoid duplicate requests.
+ if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId(), StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.Type, context.GroupId.ToString());
+ return;
+ }
+
+ var newItem = context.PreviousItemInQueue();
+ if (newItem)
+ {
+ // Send playing-queue update.
+ var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.PreviousTrack);
+ var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+ context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
+
+ // Reset status of sessions and await for all Ready events.
+ context.SetAllBuffering(true);
+ }
+ else
+ {
+ // Return to old state.
+ IGroupState newState = prevState switch
+ {
+ GroupStateType.Playing => new PlayingGroupState(Logger),
+ GroupStateType.Paused => new PausedGroupState(Logger),
+ _ => new IdleGroupState(Logger)
+ };
+
+ context.SetState(newState);
+
+ Logger.LogDebug("HandleRequest: {0} in group {1}, no previous track available.", request.Type, context.GroupId.ToString());
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/IGroupController.cs
index 2f497ebbb..038233fdd 100644
--- a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
+++ b/MediaBrowser.Controller/SyncPlay/IGroupController.cs
@@ -7,9 +7,9 @@ using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
- /// Interface ISyncPlayGroupController.
+ /// Interface IGroupController.
/// </summary>
- public interface ISyncPlayGroupController
+ public interface IGroupController
{
/// <summary>
/// Gets the group identifier.
@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <summary>
/// Checks if the group is empty.
/// </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();
/// <summary>
@@ -66,7 +66,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
/// <param name="request">The requested action.</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>
/// Gets the info about the group for the clients.
@@ -80,6 +80,5 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="user">The user.</param>
/// <returns><c>true</c> if the user can access the play queue; <c>false</c> otherwise.</returns>
bool HasAccessToPlayQueue(User user);
-
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs b/MediaBrowser.Controller/SyncPlay/IGroupPlaybackRequest.cs
new file mode 100644
index 000000000..3b195e98c
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs b/MediaBrowser.Controller/SyncPlay/IGroupState.cs
index 218e871e3..981b65221 100644
--- a/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs
+++ b/MediaBrowser.Controller/SyncPlay/IGroupState.cs
@@ -1,19 +1,19 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
- /// Interface ISyncPlayState.
+ /// Interface IGroupState.
/// </summary>
- public interface ISyncPlayState
+ public interface IGroupState
{
/// <summary>
- /// Gets the group state.
+ /// Gets the group state type.
/// </summary>
- /// <value>The group state.</value>
- GroupState GetGroupState();
+ /// <value>The group state type.</value>
+ GroupStateType Type { get; }
/// <summary>
/// Handles a session that joined the group.
@@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="prevState">The previous state.</param>
/// <param name="session">The session.</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>
/// 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="session">The session.</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>
/// Generic handle. Context's state can change.
@@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="request">The generic action.</param>
/// <param name="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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>
/// 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="session">The session.</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);
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs
index 4aac2a881..2ddaae640 100644
--- a/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs
+++ b/MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs
@@ -1,15 +1,16 @@
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
- /// Interface ISyncPlayStateContext.
+ /// Interface IGroupStateContext.
/// </summary>
- public interface ISyncPlayStateContext
+ public interface IGroupStateContext
{
/// <summary>
/// Gets the default ping value used for sessions, in milliseconds.
@@ -57,11 +58,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// Sets a new state.
/// </summary>
/// <param name="state">The new state.</param>
- void SetState(ISyncPlayState state);
+ void SetState(IGroupState state);
/// <summary>
/// Sends a GroupUpdate message to the interested sessions.
/// </summary>
+ /// <typeparam name="T">The type of the data of the message.</typeparam>
/// <param name="from">The current session.</param>
/// <param name="type">The filtering type.</param>
/// <param name="message">The message to send.</param>
@@ -89,6 +91,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <summary>
/// Builds a new group update message.
/// </summary>
+ /// <typeparam name="T">The type of the data of the message.</typeparam>
/// <param name="type">The update type.</param>
/// <param name="data">The data to send.</param>
/// <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="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>
- bool SetPlayQueue(Guid[] playQueue, int playingItemPosition, long startPositionTicks);
+ bool SetPlayQueue(IEnumerable<Guid> playQueue, int playingItemPosition, long startPositionTicks);
/// <summary>
/// Sets the playing item.
@@ -168,7 +171,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// </summary>
/// <param name="playlistItemIds">The items to remove.</param>
/// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns>
- bool RemoveFromPlayQueue(string[] playlistItemIds);
+ bool RemoveFromPlayQueue(IEnumerable<string> playlistItemIds);
/// <summary>
/// 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="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>
- bool AddToPlayQueue(Guid[] newItems, string mode);
+ bool AddToPlayQueue(IEnumerable<Guid> newItems, string mode);
/// <summary>
/// Restarts current item in play queue.
diff --git a/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs
deleted file mode 100644
index 35ca64c8d..000000000
--- a/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs
+++ /dev/null
@@ -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);
- }
-}
diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs
index ec13686c6..65146d4ae 100644
--- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs
+++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
/// <param name="request">The request.</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>
/// Maps a session to a group.
@@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
/// <param name="group">The group.</param>
/// <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>
/// Unmaps a session from a group.
@@ -64,6 +64,6 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
/// <param name="group">The group.</param>
/// <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);
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs
deleted file mode 100644
index 5466cbe2f..000000000
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs
deleted file mode 100644
index facb25155..000000000
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs
deleted file mode 100644
index 78aeb9c6f..000000000
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs
deleted file mode 100644
index f1581c98d..000000000
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs
deleted file mode 100644
index 107295208..000000000
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs
index 65fced43f..b5bed89f2 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/BufferGroupRequest.cs
@@ -1,14 +1,14 @@
using System;
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class BufferGroupRequest.
/// </summary>
- public class BufferGroupRequest : IPlaybackGroupRequest
+ public class BufferGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// 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; }
/// <summary>
- /// Gets or sets the client playback status.
+ /// Gets or sets a value indicating whether the client playback is unpaused.
/// </summary>
/// <value>The client playback status.</value>
public bool IsPlaying { get; set; }
@@ -35,15 +35,12 @@ namespace MediaBrowser.Controller.SyncPlay
public string PlaylistItemId { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.Buffer;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.Buffer;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/IgnoreWaitGroupRequest.cs
new file mode 100644
index 000000000..325839f10
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs
index 38170facf..3c95f53d4 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/MovePlaylistItemGroupRequest.cs
@@ -1,13 +1,13 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class MovePlaylistItemGroupRequest.
/// </summary>
- public class MovePlaylistItemGroupRequest : IPlaybackGroupRequest
+ public class MovePlaylistItemGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// Gets or sets the playlist identifier of the item.
@@ -22,15 +22,12 @@ namespace MediaBrowser.Controller.SyncPlay
public int NewIndex { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.MovePlaylistItem;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.MovePlaylistItem;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextTrackGroupRequest.cs
index b27c10f16..8636d6f4d 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/NextTrackGroupRequest.cs
@@ -1,13 +1,13 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class NextTrackGroupRequest.
/// </summary>
- public class NextTrackGroupRequest : IPlaybackGroupRequest
+ public class NextTrackGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// Gets or sets the playing item identifier.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
public string PlaylistItemId { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.NextTrack;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.NextTrack;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PauseGroupRequest.cs
new file mode 100644
index 000000000..45bd3b15f
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs
index 5605578d7..9dacb7985 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PingGroupRequest.cs
@@ -1,13 +1,13 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class PingGroupRequest.
/// </summary>
- public class PingGroupRequest : IPlaybackGroupRequest
+ public class PingGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// Gets or sets the ping time.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
public long Ping { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.Ping;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.Ping;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs
index f3dd769e4..e090a882e 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PlayGroupRequest.cs
@@ -1,20 +1,21 @@
using System;
+using System.Collections.Generic;
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class PlayGroupRequest.
/// </summary>
- public class PlayGroupRequest : IPlaybackGroupRequest
+ public class PlayGroupRequest : IGroupPlaybackRequest
{
/// <summary>
- /// Gets or sets the playing queue.
+ /// Gets the playing queue.
/// </summary>
/// <value>The playing queue.</value>
- public Guid[] PlayingQueue { get; set; }
+ public List<Guid> PlayingQueue { get; } = new List<Guid>();
/// <summary>
/// Gets or sets the playing item from the queue.
@@ -29,15 +30,12 @@ namespace MediaBrowser.Controller.SyncPlay
public long StartPositionTicks { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.Play;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.Play;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousTrackGroupRequest.cs
index 31b93b967..aca5d678e 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/PreviousTrackGroupRequest.cs
@@ -1,13 +1,13 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class PreviousTrackGroupRequest.
/// </summary>
- public class PreviousTrackGroupRequest : IPlaybackGroupRequest
+ public class PreviousTrackGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// Gets or sets the playing item identifier.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
public string PlaylistItemId { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.PreviousTrack;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.PreviousTrack;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs
index 01c08cc86..82380b209 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/QueueGroupRequest.cs
@@ -1,20 +1,21 @@
using System;
+using System.Collections.Generic;
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class QueueGroupRequest.
/// </summary>
- public class QueueGroupRequest : IPlaybackGroupRequest
+ public class QueueGroupRequest : IGroupPlaybackRequest
{
/// <summary>
- /// Gets or sets the items to queue.
+ /// Gets the items to queue.
/// </summary>
/// <value>The items to queue.</value>
- public Guid[] ItemIds { get; set; }
+ public List<Guid> ItemIds { get; } = new List<Guid>();
/// <summary>
/// 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; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.Queue;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.Queue;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs
index 8f266ed32..c8a2268cf 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/ReadyGroupRequest.cs
@@ -1,14 +1,14 @@
using System;
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class ReadyGroupRequest.
/// </summary>
- public class ReadyGroupRequest : IPlaybackGroupRequest
+ public class ReadyGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// 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; }
/// <summary>
- /// Gets or sets the client playback status.
+ /// Gets or sets a value indicating whether the client playback is unpaused.
/// </summary>
/// <value>The client playback status.</value>
public bool IsPlaying { get; set; }
@@ -35,15 +35,12 @@ namespace MediaBrowser.Controller.SyncPlay
public string PlaylistItemId { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.Ready;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.Ready;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/RemoveFromPlaylistGroupRequest.cs
new file mode 100644
index 000000000..4ead1301b
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs
index 24d9be507..d311bffdc 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SeekGroupRequest.cs
@@ -1,13 +1,13 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class SeekGroupRequest.
/// </summary>
- public class SeekGroupRequest : IPlaybackGroupRequest
+ public class SeekGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// Gets or sets the position ticks.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
public long PositionTicks { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.Seek;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.Seek;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs
index 070fd71d2..0983d9129 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetPlaylistItemGroupRequest.cs
@@ -1,13 +1,13 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class SetPlaylistItemGroupRequest.
/// </summary>
- public class SetPlaylistItemGroupRequest : IPlaybackGroupRequest
+ public class SetPlaylistItemGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// Gets or sets the playlist identifier of the playing item.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
public string PlaylistItemId { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.SetPlaylistItem;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.SetPlaylistItem;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs
index 5f36f60e4..79373ef5f 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetRepeatModeGroupRequest.cs
@@ -1,13 +1,13 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class SetRepeatModeGroupRequest.
/// </summary>
- public class SetRepeatModeGroupRequest : IPlaybackGroupRequest
+ public class SetRepeatModeGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// Gets or sets the repeat mode.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
public string Mode { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.SetRepeatMode;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.SetRepeatMode;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs
index 472455fd3..316fb49f4 100644
--- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs
+++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/SetShuffleModeGroupRequest.cs
@@ -1,13 +1,13 @@
using System.Threading;
-using MediaBrowser.Model.SyncPlay;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay
{
/// <summary>
/// Class SetShuffleModeGroupRequest.
/// </summary>
- public class SetShuffleModeGroupRequest : IPlaybackGroupRequest
+ public class SetShuffleModeGroupRequest : IGroupPlaybackRequest
{
/// <summary>
/// Gets or sets the shuffle mode.
@@ -16,15 +16,12 @@ namespace MediaBrowser.Controller.SyncPlay
public string Mode { get; set; }
/// <inheritdoc />
- public PlaybackRequestType GetRequestType()
- {
- return PlaybackRequestType.SetShuffleMode;
- }
+ public PlaybackRequestType Type { get; } = PlaybackRequestType.SetShuffleMode;
/// <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);
}
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/StopGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/StopGroupRequest.cs
new file mode 100644
index 000000000..9f6f8ea63
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequests/UnpauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequests/UnpauseGroupRequest.cs
new file mode 100644
index 000000000..84a6b0a6e
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
index 030005abe..8bc21a6a8 100644
--- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
+++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
@@ -5,163 +5,96 @@ using MediaBrowser.Model.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>
/// Class PlayQueueManager.
/// </summary>
public class PlayQueueManager
{
/// <summary>
- /// Gets or sets the playing item index.
+ /// Placeholder index for when no item is playing.
/// </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>
- /// Gets or sets the last time the queue has been changed.
+ /// Random number generator used to shuffle lists.
/// </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>
- /// Gets the sorted playlist.
+ /// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
/// </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>
- /// Gets the shuffled playlist.
+ /// Gets the playing item index.
/// </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>
- /// 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>
/// <value>The shuffle mode.</value>
public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted;
/// <summary>
- /// Gets or sets the repeat mode.
+ /// Gets the repeat mode.
/// </summary>
/// <value>The repeat mode.</value>
public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone;
/// <summary>
- /// Gets or sets the progressive identifier counter.
+ /// Gets or sets the sorted playlist.
/// </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>
- /// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
+ /// Gets or sets the shuffled playlist.
/// </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>
- /// Gets the next available identifier.
+ /// Gets or sets the progressive identifier counter.
/// </summary>
- /// <returns>The next available identifier.</returns>
- private int GetNextProgressiveId() {
- return ProgressiveId++;
- }
+ /// <value>The progressive identifier.</value>
+ private int ProgressiveId { get; set; }
/// <summary>
- /// Creates a list from the array of items. Each item is given an unique playlist identifier.
+ /// Checks if an item is playing.
/// </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>
- /// Gets the current playlist, depending on the shuffle mode.
+ /// Gets the current playlist considering the shuffle mode.
/// </summary>
/// <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>
/// Sets a new playlist. Playing item is reset.
/// </summary>
/// <param name="items">The new items of the playlist.</param>
- public void SetPlaylist(Guid[] items)
+ public void SetPlaylist(IEnumerable<Guid> items)
{
SortedPlaylist.Clear();
ShuffledPlaylist.Clear();
@@ -170,7 +103,7 @@ namespace MediaBrowser.Controller.SyncPlay
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
ShuffledPlaylist = SortedPlaylist.ToList();
- ShuffledPlaylist.Shuffle();
+ Shuffle(ShuffledPlaylist);
}
PlayingItemIndex = NoPlayingItemIndex;
@@ -181,7 +114,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// Appends new items to the playlist. The specified order is mantained.
/// </summary>
/// <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);
@@ -195,13 +128,14 @@ namespace MediaBrowser.Controller.SyncPlay
}
/// <summary>
- /// Shuffles the playlist. Shuffle mode is changed.
+ /// Shuffles the playlist. Shuffle mode is changed. The playlist gets re-shuffled if already shuffled.
/// </summary>
public void ShufflePlaylist()
{
- if (PlayingItemIndex == NoPlayingItemIndex) {
+ if (PlayingItemIndex == NoPlayingItemIndex)
+ {
ShuffledPlaylist = SortedPlaylist.ToList();
- ShuffledPlaylist.Shuffle();
+ Shuffle(ShuffledPlaylist);
}
else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
{
@@ -209,7 +143,7 @@ namespace MediaBrowser.Controller.SyncPlay
var playingItem = SortedPlaylist[PlayingItemIndex];
ShuffledPlaylist = SortedPlaylist.ToList();
ShuffledPlaylist.RemoveAt(PlayingItemIndex);
- ShuffledPlaylist.Shuffle();
+ Shuffle(ShuffledPlaylist);
ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList();
PlayingItemIndex = 0;
}
@@ -218,7 +152,7 @@ namespace MediaBrowser.Controller.SyncPlay
// Re-shuffle playlist.
var playingItem = ShuffledPlaylist[PlayingItemIndex];
ShuffledPlaylist.RemoveAt(PlayingItemIndex);
- ShuffledPlaylist.Shuffle();
+ Shuffle(ShuffledPlaylist);
ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList();
PlayingItemIndex = 0;
}
@@ -262,6 +196,7 @@ namespace MediaBrowser.Controller.SyncPlay
{
ShuffledPlaylist.Add(playingItem);
}
+
PlayingItemIndex = 0;
}
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.
/// </summary>
/// <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);
@@ -334,8 +269,18 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="itemId">The new playing item identifier.</param>
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;
}
@@ -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>
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;
return PlayingItemIndex != NoPlayingItemIndex;
}
@@ -358,8 +313,8 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="playlistIndex">The new playing item index.</param>
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;
}
@@ -376,7 +331,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// </summary>
/// <param name="playlistItemIds">The items to remove.</param>
/// <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 playlistItemIdsList = playlistItemIds.ToList();
@@ -396,7 +351,7 @@ namespace MediaBrowser.Controller.SyncPlay
{
// Was first element, picking next if available.
// Default to no playing item otherwise.
- PlayingItemIndex = SortedPlaylist.Count() > 0 ? 0 : NoPlayingItemIndex;
+ PlayingItemIndex = SortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex;
}
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>
public bool MovePlaylistItem(string playlistItemId, int newIndex)
{
- var list = GetPlaylistAsList();
+ var playlist = GetPlaylistInternal();
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)
{
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);
- list.Insert(newIndex, queueItem);
+ playlist.Insert(newIndex, queueItem);
LastChange = DateTime.UtcNow;
- PlayingItemIndex = list.IndexOf(playingItem);
+ PlayingItemIndex = playlist.IndexOf(playingItem);
return true;
}
@@ -505,7 +468,7 @@ namespace MediaBrowser.Controller.SyncPlay
public QueueItem GetNextItemPlaylistId()
{
int newIndex;
- var playlist = GetPlaylistAsList();
+ var playlist = GetPlaylistInternal();
switch (RepeatMode)
{
@@ -514,17 +477,18 @@ namespace MediaBrowser.Controller.SyncPlay
break;
case GroupRepeatMode.RepeatAll:
newIndex = PlayingItemIndex + 1;
- if (newIndex >= playlist.Count())
+ if (newIndex >= playlist.Count)
{
newIndex = 0;
}
+
break;
default:
newIndex = PlayingItemIndex + 1;
break;
}
- if (newIndex < 0 || newIndex >= playlist.Count())
+ if (newIndex < 0 || newIndex >= playlist.Count)
{
return null;
}
@@ -545,7 +509,7 @@ namespace MediaBrowser.Controller.SyncPlay
}
PlayingItemIndex++;
- if (PlayingItemIndex >= SortedPlaylist.Count())
+ if (PlayingItemIndex >= SortedPlaylist.Count)
{
if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
{
@@ -579,7 +543,7 @@ namespace MediaBrowser.Controller.SyncPlay
{
if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
{
- PlayingItemIndex = SortedPlaylist.Count() - 1;
+ PlayingItemIndex = SortedPlaylist.Count - 1;
}
else
{
@@ -591,5 +555,86 @@ namespace MediaBrowser.Controller.SyncPlay
LastChange = DateTime.UtcNow;
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];
+ }
+ }
}
}