public override void HandleRequest(PauseGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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); } }
public virtual void HandleRequest(RemoveFromPlaylistGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { bool playingItemRemoved; if (request.ClearPlaylist) { context.ClearPlayQueue(request.ClearPlayingItem); playingItemRemoved = request.ClearPlayingItem; } else { 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(stopRequest, context, Type, session, cancellationToken); } }
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) { _logger.LogDebug("Session {SessionId} left group {GroupId}, notifying others to resume.", session.Id, context.GroupId.ToString()); // Client, that was buffering, left the group. var playingState = new PlayingGroupState(LoggerFactory); context.SetState(playingState); var unpauseRequest = new UnpauseGroupRequest(); playingState.HandleRequest(unpauseRequest, context, Type, session, cancellationToken); } else { _logger.LogDebug("Session {SessionId} left group {GroupId}, returning to previous state.", session.Id, context.GroupId.ToString()); // Group is ready, returning to previous state. var pausedState = new PausedGroupState(LoggerFactory); context.SetState(pausedState); } } }
public virtual void HandleRequest(SetPlaylistItemGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(request, context, Type, session, cancellationToken); }
public override void HandleRequest(UnpauseGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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); } }
public override void HandleRequest(SeekGroupRequest request, 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; } 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); }
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); }
public override void HandleRequest(UnpauseGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { // Change state. var playingState = new PlayingGroupState(LoggerFactory); context.SetState(playingState); playingState.HandleRequest(request, context, Type, session, cancellationToken); }
public virtual void HandleRequest(SetShuffleModeGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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 override void HandleRequest(StopGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { // Change state. var idleState = new IdleGroupState(LoggerFactory); context.SetState(idleState); idleState.HandleRequest(request, context, Type, session, cancellationToken); }
public override void HandleRequest(PreviousItemGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { // Change state. var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(request, context, Type, session, cancellationToken); }
public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { // Wait for session to be ready. var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.SessionJoined(context, Type, session, cancellationToken); }
public override void HandleRequest(BufferGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { if (IgnoreBuffering) { return; } // Change state. var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(request, context, Type, 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); } }
public override void HandleRequest(ReadyGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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); } }
public override void HandleRequest(UnpauseGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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("Group {GroupId} is waiting for all ready events.", context.GroupId.ToString()); } else { if (ResumePlaying) { _logger.LogDebug("Forcing the playback to start in group {GroupId}. Group-wait is disabled until next state change.", 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(LoggerFactory) { IgnoreBuffering = true }; context.SetState(playingState); playingState.HandleRequest(request, context, Type, 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); } } }
public override void HandleRequest(PauseGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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); }
public override void HandleRequest(StopGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { // Save state if first event. if (!InitialStateSet) { InitialState = prevState; InitialStateSet = true; } // Change state. var idleState = new IdleGroupState(LoggerFactory); context.SetState(idleState); idleState.HandleRequest(request, context, Type, session, cancellationToken); }
public virtual void HandleRequest(MovePlaylistItemGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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 override void HandleRequest(PreviousItemGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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())) { _logger.LogDebug("Session {SessionId} provided the wrong playlist item for group {GroupId}.", session.Id, context.GroupId.ToString()); return; } var newItem = context.PreviousItemInQueue(); if (newItem) { // Send playing-queue update. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.PreviousItem); 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(LoggerFactory), GroupStateType.Paused => new PausedGroupState(LoggerFactory), _ => new IdleGroupState(LoggerFactory) }; context.SetState(newState); _logger.LogDebug("No previous item available in group {GroupId}.", context.GroupId.ToString()); } }
public override void HandleRequest(ReadyGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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); } }
public virtual void HandleRequest(QueueGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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 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); }
public override void HandleRequest(PlayGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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("Unable to set playing queue in group {GroupId}.", context.GroupId.ToString()); // Ignore request and return to previous state. IGroupState newState = prevState switch { GroupStateType.Playing => new PlayingGroupState(LoggerFactory), GroupStateType.Paused => new PausedGroupState(LoggerFactory), _ => new IdleGroupState(LoggerFactory) }; 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("Session {SessionId} set a new play queue in group {GroupId}.", session.Id, context.GroupId.ToString()); }
public override void HandleRequest(SetPlaylistItemGroupRequest request, IGroupStateContext context, GroupStateType prevState, 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(LoggerFactory), GroupStateType.Paused => new PausedGroupState(LoggerFactory), _ => new IdleGroupState(LoggerFactory) }; context.SetState(newState); _logger.LogDebug("Unable to change current playing item in group {GroupId}.", context.GroupId.ToString()); } }
public override void HandleRequest(IgnoreWaitGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { context.SetIgnoreGroupWait(session, request.IgnoreWait); if (!context.IsBuffering()) { _logger.LogDebug("Ignoring session {SessionId}, group {GroupId} is ready.", session.Id, context.GroupId.ToString()); if (ResumePlaying) { // Client, that was buffering, stopped following playback. var playingState = new PlayingGroupState(LoggerFactory); context.SetState(playingState); var unpauseRequest = new UnpauseGroupRequest(); playingState.HandleRequest(unpauseRequest, context, Type, session, cancellationToken); } else { // Group is ready, returning to previous state. var pausedState = new PausedGroupState(LoggerFactory); context.SetState(pausedState); } } }
public override void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { // Do nothing. }
public virtual void HandleRequest(PlayGroupRequest request, IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { UnhandledRequest(request); }
public override void Apply(IGroupStateContext context, IGroupState state, SessionInfo session, CancellationToken cancellationToken) { state.HandleRequest(this, context, state.Type, session, cancellationToken); }
public abstract void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken);