using System; 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 PlayingGroupState. /// /// /// Class is not thread-safe, external locking is required when accessing methods. /// public class PlayingGroupState : AbstractGroupState { /// /// The logger. /// private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// Instance of the interface. public PlayingGroupState(ILoggerFactory loggerFactory) : base(loggerFactory) { _logger = LoggerFactory.CreateLogger(); } /// public override GroupStateType Type { get; } = GroupStateType.Playing; /// /// Gets or sets a value indicating whether requests for buffering should be ignored. /// public bool IgnoreBuffering { get; set; } /// 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 SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, CancellationToken cancellationToken) { // Do nothing. } /// public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state. var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(context, Type, request, session, cancellationToken); } /// 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); } } /// public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state. var pausedState = new PausedGroupState(LoggerFactory); context.SetState(pausedState); pausedState.HandleRequest(context, Type, request, session, cancellationToken); } /// public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state. var idleState = new IdleGroupState(LoggerFactory); context.SetState(idleState); idleState.HandleRequest(context, Type, request, session, cancellationToken); } /// public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state. var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(context, Type, request, session, cancellationToken); } /// public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { if (IgnoreBuffering) { return; } // Change state. var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(context, Type, request, session, cancellationToken); } /// 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); } } /// public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state. var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(context, Type, request, session, cancellationToken); } /// public override void HandleRequest(IGroupStateContext context, GroupStateType prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state. var waitingState = new WaitingGroupState(LoggerFactory); context.SetState(waitingState); waitingState.HandleRequest(context, Type, request, session, cancellationToken); } } }