using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; using MediaBrowser.Model.SyncPlay; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.SyncPlay.GroupStates { /// /// Class AbstractGroupState. /// /// /// Class is not thread-safe, external locking is required when accessing methods. /// public abstract class AbstractGroupState : IGroupState { /// /// The logger. /// private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// Instance of the interface. protected AbstractGroupState(ILoggerFactory loggerFactory) { LoggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); } /// public abstract GroupStateType Type { get; } /// /// Gets the logger factory. /// protected ILoggerFactory LoggerFactory { get; } /// public abstract void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken); /// public abstract void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken); /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, IGroupPlaybackRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(context, Type, request, session, cancellationToken); } /// 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("Play queue in group {GroupId} is now empty.", context.GroupId.ToString()); IGroupState idleState = new IdleGroupState(LoggerFactory); context.SetState(idleState); var stopRequest = new StopGroupRequest(); idleState.HandleRequest(context, Type, stopRequest, session, cancellationToken); } } /// 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("Unable to move item in group {GroupId}.", context.GroupId.ToString()); return; } var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.MoveItem); var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); } /// 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("Unable to add items to play queue in group {GroupId}.", context.GroupId.ToString()); return; } var reason = request.Mode switch { GroupQueueMode.QueueNext => PlayQueueUpdateReason.QueueNext, _ => PlayQueueUpdateReason.Queue }; var playQueueUpdate = context.GetPlayQueueUpdate(reason); var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); } /// 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); } /// 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); } /// 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); } /// public virtual void HandleRequest(IGroupStateContext context, GroupStateType prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { context.SetIgnoreGroupWait(session, request.IgnoreWait); } /// /// Sends a group state update to all group. /// /// The context of the state. /// The reason of the state change. /// The session. /// The cancellation token. protected void SendGroupStateUpdate(IGroupStateContext context, IGroupPlaybackRequest reason, SessionInfo session, CancellationToken cancellationToken) { // Notify relevant state change event. var stateUpdate = new GroupStateUpdate(Type, reason.Action); var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.StateUpdate, stateUpdate); context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); } private void UnhandledRequest(IGroupPlaybackRequest request) { _logger.LogWarning("Unhandled request of type {RequestType} in {StateType} state.", request.Action, Type); } } }