2020-09-24 23:04:21 +02:00
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>
2020-11-13 15:13:32 +01:00
/// Initializes a new instance of the <see cref="WaitingGroupState"/> class.
2020-09-24 23:04:21 +02:00
/// </summary>
2020-11-13 15:13:32 +01:00
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
2020-10-22 15:51:58 +02:00
public WaitingGroupState ( ILogger logger )
: base ( logger )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Do nothing.
2020-09-24 23:04:21 +02:00
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override GroupStateType Type
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
get
{
return GroupStateType . Waiting ;
}
2020-09-24 23:04:21 +02:00
}
2020-11-13 15:13:32 +01:00
/// <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 ; }
2020-09-24 23:04:21 +02:00
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void SessionJoined ( IGroupStateContext context , GroupStateType prevState , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
2020-11-13 15:13:32 +01:00
if ( prevState . Equals ( GroupStateType . Playing ) )
{
2020-09-24 23:04:21 +02:00
ResumePlaying = true ;
2020-10-21 15:46:50 +02:00
// Pause group and compute the media playback position.
2020-09-24 23:04:21 +02:00
var currentTime = DateTime . UtcNow ;
var elapsedTime = currentTime - context . LastActivity ;
context . LastActivity = currentTime ;
2020-10-22 15:51:58 +02:00
// 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.
2020-10-21 15:46:50 +02:00
// Seek only if playback actually started.
2020-10-21 16:42:57 +02:00
context . PositionTicks + = Math . Max ( elapsedTime . Ticks , 0 ) ;
2020-09-24 23:04:21 +02:00
}
2020-10-21 15:46:50 +02:00
// Prepare new session.
2020-09-24 23:04:21 +02:00
var playQueueUpdate = context . GetPlayQueueUpdate ( PlayQueueUpdateReason . NewPlaylist ) ;
var update = context . NewSyncPlayGroupUpdate ( GroupUpdateType . PlayQueue , playQueueUpdate ) ;
context . SendGroupUpdate ( session , SyncPlayBroadcastType . CurrentSession , update , cancellationToken ) ;
context . SetBuffering ( session , true ) ;
2020-10-21 15:46:50 +02:00
// Send pause command to all non-buffering sessions.
2020-09-24 23:04:21 +02:00
var command = context . NewSyncPlayCommand ( SendCommandType . Pause ) ;
context . SendCommand ( session , SyncPlayBroadcastType . AllReady , command , cancellationToken ) ;
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void SessionLeaving ( IGroupStateContext context , GroupStateType prevState , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
context . SetBuffering ( session , false ) ;
if ( ! context . IsBuffering ( ) )
{
if ( ResumePlaying )
{
2020-10-21 15:46:50 +02:00
// Client, that was buffering, left the group.
2020-11-13 15:13:32 +01:00
var playingState = new PlayingGroupState ( Logger ) ;
2020-09-24 23:04:21 +02:00
context . SetState ( playingState ) ;
var unpauseRequest = new UnpauseGroupRequest ( ) ;
2020-11-13 15:13:32 +01:00
playingState . HandleRequest ( context , Type , unpauseRequest , session , cancellationToken ) ;
2020-09-24 23:04:21 +02:00
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "SessionLeaving: {0} left the group {1}, notifying others to resume." , session . Id , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
}
else
{
2020-10-21 15:46:50 +02:00
// Group is ready, returning to previous state.
2020-11-13 15:13:32 +01:00
var pausedState = new PausedGroupState ( Logger ) ;
2020-09-24 23:04:21 +02:00
context . SetState ( pausedState ) ;
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "SessionLeaving: {0} left the group {1}, returning to previous state." , session . Id , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
}
}
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , PlayGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
ResumePlaying = true ;
var setQueueStatus = context . SetPlayQueue ( request . PlayingQueue , request . PlayingItemPosition , request . StartPositionTicks ) ;
if ( ! setQueueStatus )
{
2020-11-13 15:13:32 +01:00
Logger . LogError ( "HandleRequest: {0} in group {1}, unable to set playing queue." , request . Type , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
2020-10-21 15:46:50 +02:00
// Ignore request and return to previous state.
2020-11-13 15:13:32 +01:00
IGroupState newState = prevState switch {
GroupStateType . Playing = > new PlayingGroupState ( Logger ) ,
GroupStateType . Paused = > new PausedGroupState ( Logger ) ,
_ = > new IdleGroupState ( Logger )
} ;
2020-09-24 23:04:21 +02:00
context . SetState ( newState ) ;
return ;
}
var playQueueUpdate = context . GetPlayQueueUpdate ( PlayQueueUpdateReason . NewPlaylist ) ;
var update = context . NewSyncPlayGroupUpdate ( GroupUpdateType . PlayQueue , playQueueUpdate ) ;
context . SendGroupUpdate ( session , SyncPlayBroadcastType . AllGroup , update , cancellationToken ) ;
2020-10-21 16:42:57 +02:00
// Reset status of sessions and await for all Ready events.
2020-09-24 23:04:21 +02:00
context . SetAllBuffering ( true ) ;
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, {2} set a new play queue." , request . Type , context . GroupId . ToString ( ) , session . Id ) ;
2020-09-24 23:04:21 +02:00
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , SetPlaylistItemGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
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 ) ;
2020-10-21 16:42:57 +02:00
// Reset status of sessions and await for all Ready events.
2020-09-24 23:04:21 +02:00
context . SetAllBuffering ( true ) ;
}
else
{
2020-10-21 15:46:50 +02:00
// Return to old state.
2020-11-13 15:13:32 +01:00
IGroupState newState = prevState switch
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
GroupStateType . Playing = > new PlayingGroupState ( Logger ) ,
GroupStateType . Paused = > new PausedGroupState ( Logger ) ,
_ = > new IdleGroupState ( Logger )
} ;
2020-09-24 23:04:21 +02:00
context . SetState ( newState ) ;
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, unable to change current playing item." , request . Type , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
}
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , UnpauseGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
2020-11-13 15:13:32 +01:00
if ( prevState . Equals ( GroupStateType . Idle ) )
2020-09-24 23:04:21 +02:00
{
ResumePlaying = true ;
context . RestartCurrentItem ( ) ;
var playQueueUpdate = context . GetPlayQueueUpdate ( PlayQueueUpdateReason . NewPlaylist ) ;
var update = context . NewSyncPlayGroupUpdate ( GroupUpdateType . PlayQueue , playQueueUpdate ) ;
context . SendGroupUpdate ( session , SyncPlayBroadcastType . AllGroup , update , cancellationToken ) ;
2020-10-21 16:42:57 +02:00
// Reset status of sessions and await for all Ready events.
2020-09-24 23:04:21 +02:00
context . SetAllBuffering ( true ) ;
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, waiting for all ready events." , request . Type , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
}
else
{
if ( ResumePlaying )
{
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, ignoring sessions that are not ready and forcing the playback to start." , request . Type , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
2020-10-21 15:46:50 +02:00
// An Unpause request is forcing the playback to start, ignoring sessions that are not ready.
2020-09-24 23:04:21 +02:00
context . SetAllBuffering ( false ) ;
2020-10-21 15:46:50 +02:00
// Change state.
2020-11-13 15:13:32 +01:00
var playingState = new PlayingGroupState ( Logger )
{
IgnoreBuffering = true
} ;
2020-09-24 23:04:21 +02:00
context . SetState ( playingState ) ;
2020-11-13 15:13:32 +01:00
playingState . HandleRequest ( context , Type , request , session , cancellationToken ) ;
2020-09-24 23:04:21 +02:00
}
else
{
2020-10-21 15:46:50 +02:00
// Group would have gone to paused state, now will go to playing state when ready.
2020-09-24 23:04:21 +02:00
ResumePlaying = true ;
2020-10-21 15:46:50 +02:00
// Notify relevant state change event.
2020-09-24 23:04:21 +02:00
SendGroupStateUpdate ( context , request , session , cancellationToken ) ;
}
}
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , PauseGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
2020-10-21 15:46:50 +02:00
// Wait for sessions to be ready, then switch to paused state.
2020-09-24 23:04:21 +02:00
ResumePlaying = false ;
2020-10-21 15:46:50 +02:00
// Notify relevant state change event.
2020-09-24 23:04:21 +02:00
SendGroupStateUpdate ( context , request , session , cancellationToken ) ;
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , StopGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
2020-10-21 15:46:50 +02:00
// Change state.
2020-11-13 15:13:32 +01:00
var idleState = new IdleGroupState ( Logger ) ;
2020-09-24 23:04:21 +02:00
context . SetState ( idleState ) ;
2020-11-13 15:13:32 +01:00
idleState . HandleRequest ( context , Type , request , session , cancellationToken ) ;
2020-09-24 23:04:21 +02:00
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , SeekGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
2020-11-13 15:13:32 +01:00
if ( prevState . Equals ( GroupStateType . Playing ) )
2020-09-24 23:04:21 +02:00
{
ResumePlaying = true ;
}
2020-11-13 15:13:32 +01:00
else if ( prevState . Equals ( GroupStateType . Paused ) )
2020-09-24 23:04:21 +02:00
{
ResumePlaying = false ;
}
2020-10-21 15:46:50 +02:00
// Sanitize PositionTicks.
2020-09-24 23:04:21 +02:00
var ticks = context . SanitizePositionTicks ( request . PositionTicks ) ;
2020-10-21 15:46:50 +02:00
// Seek.
2020-09-24 23:04:21 +02:00
context . PositionTicks = ticks ;
context . LastActivity = DateTime . UtcNow ;
var command = context . NewSyncPlayCommand ( SendCommandType . Seek ) ;
context . SendCommand ( session , SyncPlayBroadcastType . AllGroup , command , cancellationToken ) ;
2020-10-21 16:42:57 +02:00
// Reset status of sessions and await for all Ready events.
2020-09-24 23:04:21 +02:00
context . SetAllBuffering ( true ) ;
2020-10-21 15:46:50 +02:00
// Notify relevant state change event.
2020-09-24 23:04:21 +02:00
SendGroupStateUpdate ( context , request , session , cancellationToken ) ;
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , BufferGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
2020-10-21 15:46:50 +02:00
// Make sure the client is playing the correct item.
2020-11-13 15:13:32 +01:00
if ( ! request . PlaylistItemId . Equals ( context . PlayQueue . GetPlayingItemPlaylistId ( ) , StringComparison . OrdinalIgnoreCase ) )
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, {2} has wrong playlist item." , request . Type , context . GroupId . ToString ( ) , session . Id ) ;
2020-09-24 23:04:21 +02:00
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 ;
}
2020-11-13 15:13:32 +01:00
if ( prevState . Equals ( GroupStateType . Playing ) )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Resume playback when all ready.
2020-09-24 23:04:21 +02:00
ResumePlaying = true ;
context . SetBuffering ( session , true ) ;
2020-10-21 15:46:50 +02:00
// Pause group and compute the media playback position.
2020-09-24 23:04:21 +02:00
var currentTime = DateTime . UtcNow ;
var elapsedTime = currentTime - context . LastActivity ;
context . LastActivity = currentTime ;
2020-10-22 15:51:58 +02:00
// 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.
2020-10-21 16:42:57 +02:00
context . PositionTicks + = Math . Max ( elapsedTime . Ticks , 0 ) ;
2020-09-24 23:04:21 +02:00
2020-10-21 15:46:50 +02:00
// Send pause command to all non-buffering sessions.
2020-09-24 23:04:21 +02:00
var command = context . NewSyncPlayCommand ( SendCommandType . Pause ) ;
context . SendCommand ( session , SyncPlayBroadcastType . AllReady , command , cancellationToken ) ;
}
2020-11-13 15:13:32 +01:00
else if ( prevState . Equals ( GroupStateType . Paused ) )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Don't resume playback when all ready.
2020-09-24 23:04:21 +02:00
ResumePlaying = false ;
context . SetBuffering ( session , true ) ;
2020-10-21 15:46:50 +02:00
// Send pause command to buffering session.
2020-09-24 23:04:21 +02:00
var command = context . NewSyncPlayCommand ( SendCommandType . Pause ) ;
context . SendCommand ( session , SyncPlayBroadcastType . CurrentSession , command , cancellationToken ) ;
}
2020-11-13 15:13:32 +01:00
else if ( prevState . Equals ( GroupStateType . Waiting ) )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Another session is now buffering.
2020-09-24 23:04:21 +02:00
context . SetBuffering ( session , true ) ;
if ( ! ResumePlaying )
{
2020-10-21 15:46:50 +02:00
// Force update for this session that should be paused.
2020-09-24 23:04:21 +02:00
var command = context . NewSyncPlayCommand ( SendCommandType . Pause ) ;
context . SendCommand ( session , SyncPlayBroadcastType . CurrentSession , command , cancellationToken ) ;
}
}
2020-10-21 15:46:50 +02:00
// Notify relevant state change event.
2020-09-24 23:04:21 +02:00
SendGroupStateUpdate ( context , request , session , cancellationToken ) ;
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , ReadyGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
2020-10-21 15:46:50 +02:00
// Make sure the client is playing the correct item.
2020-11-13 15:13:32 +01:00
if ( ! request . PlaylistItemId . Equals ( context . PlayQueue . GetPlayingItemPlaylistId ( ) , StringComparison . OrdinalIgnoreCase ) )
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, {2} has wrong playlist item." , request . Type , context . GroupId . ToString ( ) , session . Id ) ;
2020-09-24 23:04:21 +02:00
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 ;
}
2020-11-08 18:08:59 +01:00
// 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.
2020-09-24 23:04:21 +02:00
var currentTime = DateTime . UtcNow ;
2020-11-08 18:08:59 +01:00
var elapsedTime = currentTime . Subtract ( request . When ) ;
var timeSyncThresholdTicks = TimeSpan . FromMilliseconds ( context . TimeSyncOffset ) . Ticks ;
if ( Math . Abs ( elapsedTime . Ticks ) > timeSyncThresholdTicks )
{
2020-11-13 15:13:32 +01:00
Logger . LogWarning ( "HandleRequest: {0} in group {1}, {2} is not time syncing properly. Ignoring elapsed time." , request . Type , context . GroupId . ToString ( ) , session . Id ) ;
2020-11-08 18:08:59 +01:00
elapsedTime = TimeSpan . Zero ;
}
// Ignore elapsed time if client is paused.
2020-09-24 23:04:21 +02:00
if ( ! request . IsPlaying )
{
elapsedTime = TimeSpan . Zero ;
}
2020-11-08 18:08:59 +01:00
var requestTicks = context . SanitizePositionTicks ( request . PositionTicks ) ;
2020-09-24 23:04:21 +02:00
var clientPosition = TimeSpan . FromTicks ( requestTicks ) + elapsedTime ;
var delayTicks = context . PositionTicks - clientPosition . Ticks ;
2020-11-08 18:08:59 +01:00
var maxPlaybackOffsetTicks = TimeSpan . FromMilliseconds ( context . MaxPlaybackOffset ) . Ticks ;
2020-09-24 23:04:21 +02:00
2020-11-13 15:13:32 +01:00
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 ) ;
2020-09-24 23:04:21 +02:00
if ( ResumePlaying )
{
// Handle case where session reported as ready but in reality
2020-10-21 15:46:50 +02:00
// it has no clue of the real position nor the playback state.
2020-11-08 18:08:59 +01:00
if ( ! request . IsPlaying & & Math . Abs ( delayTicks ) > maxPlaybackOffsetTicks )
{
2020-10-21 15:46:50 +02:00
// Session not ready at all.
2020-09-24 23:04:21 +02:00
context . SetBuffering ( session , true ) ;
2020-10-21 15:46:50 +02:00
// Correcting session's position.
2020-09-24 23:04:21 +02:00
var command = context . NewSyncPlayCommand ( SendCommandType . Seek ) ;
context . SendCommand ( session , SyncPlayBroadcastType . CurrentSession , command , cancellationToken ) ;
2020-10-21 15:46:50 +02:00
// Notify relevant state change event.
2020-09-24 23:04:21 +02:00
SendGroupStateUpdate ( context , request , session , cancellationToken ) ;
2020-11-13 15:13:32 +01:00
Logger . LogWarning ( "HandleRequest: {0} in group {1}, {2} got lost in time, correcting." , request . Type , context . GroupId . ToString ( ) , session . Id ) ;
2020-09-24 23:04:21 +02:00
return ;
}
2020-10-21 15:46:50 +02:00
// Session is ready.
2020-09-24 23:04:21 +02:00
context . SetBuffering ( session , false ) ;
if ( context . IsBuffering ( ) )
{
2020-10-21 15:46:50 +02:00
// Others are still buffering, tell this client to pause when ready.
2020-09-24 23:04:21 +02:00
var command = context . NewSyncPlayCommand ( SendCommandType . Pause ) ;
var pauseAtTime = currentTime . AddTicks ( delayTicks ) ;
command . When = context . DateToUTCString ( pauseAtTime ) ;
context . SendCommand ( session , SyncPlayBroadcastType . CurrentSession , command , cancellationToken ) ;
2020-11-13 15:13:32 +01:00
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 ) ;
2020-09-24 23:04:21 +02:00
}
else
{
2020-10-21 15:46:50 +02:00
// If all ready, then start playback.
// Let other clients resume as soon as the buffering client catches up.
2020-09-24 23:04:21 +02:00
if ( delayTicks > context . GetHighestPing ( ) * 2 * TimeSpan . TicksPerMillisecond )
{
2020-10-21 15:46:50 +02:00
// Client that was buffering is recovering, notifying others to resume.
2020-09-24 23:04:21 +02:00
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 ) ;
2020-11-13 15:13:32 +01:00
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 ) ;
2020-09-24 23:04:21 +02:00
}
else
{
2020-10-21 15:46:50 +02:00
// Client, that was buffering, resumed playback but did not update others in time.
2020-09-24 23:04:21 +02:00
delayTicks = context . GetHighestPing ( ) * 2 * TimeSpan . TicksPerMillisecond ;
2020-10-22 15:51:58 +02:00
delayTicks = Math . Max ( delayTicks , context . DefaultPing ) ;
2020-09-24 23:04:21 +02:00
context . LastActivity = currentTime . AddTicks ( delayTicks ) ;
var command = context . NewSyncPlayCommand ( SendCommandType . Unpause ) ;
context . SendCommand ( session , SyncPlayBroadcastType . AllGroup , command , cancellationToken ) ;
2020-11-13 15:13:32 +01:00
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 ) ;
2020-09-24 23:04:21 +02:00
}
2020-10-21 15:46:50 +02:00
// Change state.
2020-11-13 15:13:32 +01:00
var playingState = new PlayingGroupState ( Logger ) ;
2020-09-24 23:04:21 +02:00
context . SetState ( playingState ) ;
2020-11-13 15:13:32 +01:00
playingState . HandleRequest ( context , Type , request , session , cancellationToken ) ;
2020-09-24 23:04:21 +02:00
}
}
else
{
2020-11-08 18:08:59 +01:00
// Check that session is really ready, tollerate player imperfections under a certain threshold.
if ( Math . Abs ( context . PositionTicks - requestTicks ) > maxPlaybackOffsetTicks )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Session still not ready.
2020-09-24 23:04:21 +02:00
context . SetBuffering ( session , true ) ;
2020-10-21 15:46:50 +02:00
// Session is seeking to wrong position, correcting.
2020-09-24 23:04:21 +02:00
var command = context . NewSyncPlayCommand ( SendCommandType . Seek ) ;
context . SendCommand ( session , SyncPlayBroadcastType . CurrentSession , command , cancellationToken ) ;
2020-10-21 15:46:50 +02:00
// Notify relevant state change event.
2020-09-24 23:04:21 +02:00
SendGroupStateUpdate ( context , request , session , cancellationToken ) ;
2020-11-13 15:13:32 +01:00
Logger . LogWarning ( "HandleRequest: {0} in group {1}, {2} was seeking to wrong position, correcting." , request . Type , context . GroupId . ToString ( ) , session . Id ) ;
2020-09-24 23:04:21 +02:00
return ;
2020-11-13 15:13:32 +01:00
}
else
{
2020-10-21 15:46:50 +02:00
// Session is ready.
2020-09-24 23:04:21 +02:00
context . SetBuffering ( session , false ) ;
}
if ( ! context . IsBuffering ( ) )
{
2020-10-21 15:46:50 +02:00
// Group is ready, returning to previous state.
2020-11-13 15:13:32 +01:00
var pausedState = new PausedGroupState ( Logger ) ;
2020-09-24 23:04:21 +02:00
context . SetState ( pausedState ) ;
2020-11-13 15:13:32 +01:00
if ( InitialState . Equals ( GroupStateType . Playing ) )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Group went from playing to waiting state and a pause request occured while waiting.
2020-09-24 23:04:21 +02:00
var pauserequest = new PauseGroupRequest ( ) ;
2020-11-13 15:13:32 +01:00
pausedState . HandleRequest ( context , Type , pauserequest , session , cancellationToken ) ;
2020-09-24 23:04:21 +02:00
}
2020-11-13 15:13:32 +01:00
else if ( InitialState . Equals ( GroupStateType . Paused ) )
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
pausedState . HandleRequest ( context , Type , request , session , cancellationToken ) ;
2020-09-24 23:04:21 +02:00
}
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, {2} is ready, returning to previous state." , request . Type , context . GroupId . ToString ( ) , session . Id ) ;
2020-09-24 23:04:21 +02:00
}
}
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , NextTrackGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
ResumePlaying = true ;
2020-10-21 15:46:50 +02:00
// Make sure the client knows the playing item, to avoid duplicate requests.
2020-11-13 15:13:32 +01:00
if ( ! request . PlaylistItemId . Equals ( context . PlayQueue . GetPlayingItemPlaylistId ( ) , StringComparison . OrdinalIgnoreCase ) )
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, client provided the wrong playlist identifier." , request . Type , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
return ;
}
var newItem = context . NextItemInQueue ( ) ;
if ( newItem )
{
2020-10-21 15:46:50 +02:00
// Send playing-queue update.
2020-09-24 23:04:21 +02:00
var playQueueUpdate = context . GetPlayQueueUpdate ( PlayQueueUpdateReason . NextTrack ) ;
var update = context . NewSyncPlayGroupUpdate ( GroupUpdateType . PlayQueue , playQueueUpdate ) ;
context . SendGroupUpdate ( session , SyncPlayBroadcastType . AllGroup , update , cancellationToken ) ;
2020-10-21 16:42:57 +02:00
// Reset status of sessions and await for all Ready events.
2020-09-24 23:04:21 +02:00
context . SetAllBuffering ( true ) ;
}
else
{
2020-10-21 15:46:50 +02:00
// Return to old state.
2020-11-13 15:13:32 +01:00
IGroupState newState = prevState switch
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
GroupStateType . Playing = > new PlayingGroupState ( Logger ) ,
GroupStateType . Paused = > new PausedGroupState ( Logger ) ,
_ = > new IdleGroupState ( Logger )
} ;
2020-09-24 23:04:21 +02:00
context . SetState ( newState ) ;
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, no next track available." , request . Type , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
}
}
/// <inheritdoc />
2020-11-13 15:13:32 +01:00
public override void HandleRequest ( IGroupStateContext context , GroupStateType prevState , PreviousTrackGroupRequest request , SessionInfo session , CancellationToken cancellationToken )
2020-09-24 23:04:21 +02:00
{
2020-10-21 15:46:50 +02:00
// Save state if first event.
2020-09-24 23:04:21 +02:00
if ( ! InitialStateSet )
{
InitialState = prevState ;
InitialStateSet = true ;
}
ResumePlaying = true ;
2020-10-21 15:46:50 +02:00
// Make sure the client knows the playing item, to avoid duplicate requests.
2020-11-13 15:13:32 +01:00
if ( ! request . PlaylistItemId . Equals ( context . PlayQueue . GetPlayingItemPlaylistId ( ) , StringComparison . OrdinalIgnoreCase ) )
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, client provided the wrong playlist identifier." , request . Type , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
return ;
}
var newItem = context . PreviousItemInQueue ( ) ;
if ( newItem )
{
2020-10-21 15:46:50 +02:00
// Send playing-queue update.
2020-09-24 23:04:21 +02:00
var playQueueUpdate = context . GetPlayQueueUpdate ( PlayQueueUpdateReason . PreviousTrack ) ;
var update = context . NewSyncPlayGroupUpdate ( GroupUpdateType . PlayQueue , playQueueUpdate ) ;
context . SendGroupUpdate ( session , SyncPlayBroadcastType . AllGroup , update , cancellationToken ) ;
2020-10-21 16:42:57 +02:00
// Reset status of sessions and await for all Ready events.
2020-09-24 23:04:21 +02:00
context . SetAllBuffering ( true ) ;
}
else
{
2020-10-21 15:46:50 +02:00
// Return to old state.
2020-11-13 15:13:32 +01:00
IGroupState newState = prevState switch
2020-09-24 23:04:21 +02:00
{
2020-11-13 15:13:32 +01:00
GroupStateType . Playing = > new PlayingGroupState ( Logger ) ,
GroupStateType . Paused = > new PausedGroupState ( Logger ) ,
_ = > new IdleGroupState ( Logger )
} ;
2020-09-24 23:04:21 +02:00
context . SetState ( newState ) ;
2020-11-13 15:13:32 +01:00
Logger . LogDebug ( "HandleRequest: {0} in group {1}, no previous track available." , request . Type , context . GroupId . ToString ( ) ) ;
2020-09-24 23:04:21 +02:00
}
}
}
}