|
@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Session;
|
|
|
using MediaBrowser.Controller.SyncPlay;
|
|
|
using MediaBrowser.Model.Session;
|
|
|
using MediaBrowser.Model.SyncPlay;
|
|
|
+using Microsoft.Extensions.Logging;
|
|
|
|
|
|
namespace Emby.Server.Implementations.SyncPlay
|
|
|
{
|
|
@@ -17,34 +18,8 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
/// <remarks>
|
|
|
/// Class is not thread-safe, external locking is required when accessing methods.
|
|
|
/// </remarks>
|
|
|
- public class SyncPlayController : ISyncPlayController
|
|
|
+ public class SyncPlayController : ISyncPlayController, ISyncPlayStateContext
|
|
|
{
|
|
|
- /// <summary>
|
|
|
- /// Used to filter the sessions of a group.
|
|
|
- /// </summary>
|
|
|
- private enum BroadcastType
|
|
|
- {
|
|
|
- /// <summary>
|
|
|
- /// All sessions will receive the message.
|
|
|
- /// </summary>
|
|
|
- AllGroup = 0,
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Only the specified session will receive the message.
|
|
|
- /// </summary>
|
|
|
- CurrentSession = 1,
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// All sessions, except the current one, will receive the message.
|
|
|
- /// </summary>
|
|
|
- AllExceptCurrentSession = 2,
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Only sessions that are not buffering will receive the message.
|
|
|
- /// </summary>
|
|
|
- AllReady = 3
|
|
|
- }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// The session manager.
|
|
|
/// </summary>
|
|
@@ -55,22 +30,33 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
/// </summary>
|
|
|
private readonly ISyncPlayManager _syncPlayManager;
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// The logger.
|
|
|
+ /// </summary>
|
|
|
+ private readonly ILogger _logger;
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// The group to manage.
|
|
|
/// </summary>
|
|
|
private readonly GroupInfo _group = new GroupInfo();
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Initializes a new instance of the <see cref="SyncPlayController" /> class.
|
|
|
+ /// Internal group state.
|
|
|
/// </summary>
|
|
|
- /// <param name="sessionManager">The session manager.</param>
|
|
|
- /// <param name="syncPlayManager">The SyncPlay manager.</param>
|
|
|
- public SyncPlayController(
|
|
|
- ISessionManager sessionManager,
|
|
|
- ISyncPlayManager syncPlayManager)
|
|
|
+ /// <value>The group's state.</value>
|
|
|
+ private ISyncPlayState State = new PausedGroupState();
|
|
|
+
|
|
|
+ /// <inheritdoc />
|
|
|
+ public GroupInfo GetGroup()
|
|
|
{
|
|
|
- _sessionManager = sessionManager;
|
|
|
- _syncPlayManager = syncPlayManager;
|
|
|
+ return _group;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc />
|
|
|
+ public void SetState(ISyncPlayState state)
|
|
|
+ {
|
|
|
+ _logger.LogInformation("SetState: {0} -> {1}.", State.GetGroupState(), state.GetGroupState());
|
|
|
+ this.State = state;
|
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
@@ -83,13 +69,18 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
public bool IsGroupEmpty() => _group.IsEmpty();
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Converts DateTime to UTC string.
|
|
|
+ /// Initializes a new instance of the <see cref="SyncPlayController" /> class.
|
|
|
/// </summary>
|
|
|
- /// <param name="date">The date to convert.</param>
|
|
|
- /// <value>The UTC string.</value>
|
|
|
- private string DateToUTCString(DateTime date)
|
|
|
+ /// <param name="sessionManager">The session manager.</param>
|
|
|
+ /// <param name="syncPlayManager">The SyncPlay manager.</param>
|
|
|
+ public SyncPlayController(
|
|
|
+ ISessionManager sessionManager,
|
|
|
+ ISyncPlayManager syncPlayManager,
|
|
|
+ ILogger logger)
|
|
|
{
|
|
|
- return date.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture);
|
|
|
+ _sessionManager = sessionManager;
|
|
|
+ _syncPlayManager = syncPlayManager;
|
|
|
+ _logger = logger;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -98,37 +89,30 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
/// <param name="from">The current session.</param>
|
|
|
/// <param name="type">The filtering type.</param>
|
|
|
/// <value>The array of sessions matching the filter.</value>
|
|
|
- private IEnumerable<SessionInfo> FilterSessions(SessionInfo from, BroadcastType type)
|
|
|
+ private SessionInfo[] FilterSessions(SessionInfo from, SyncPlayBroadcastType type)
|
|
|
{
|
|
|
switch (type)
|
|
|
{
|
|
|
- case BroadcastType.CurrentSession:
|
|
|
+ case SyncPlayBroadcastType.CurrentSession:
|
|
|
return new SessionInfo[] { from };
|
|
|
- case BroadcastType.AllGroup:
|
|
|
- return _group.Participants.Values
|
|
|
- .Select(session => session.Session);
|
|
|
- case BroadcastType.AllExceptCurrentSession:
|
|
|
- return _group.Participants.Values
|
|
|
- .Select(session => session.Session)
|
|
|
- .Where(session => !session.Id.Equals(from.Id, StringComparison.Ordinal));
|
|
|
- case BroadcastType.AllReady:
|
|
|
- return _group.Participants.Values
|
|
|
- .Where(session => !session.IsBuffering)
|
|
|
- .Select(session => session.Session);
|
|
|
+ case SyncPlayBroadcastType.AllGroup:
|
|
|
+ return _group.Participants.Values.Select(
|
|
|
+ session => session.Session).ToArray();
|
|
|
+ case SyncPlayBroadcastType.AllExceptCurrentSession:
|
|
|
+ return _group.Participants.Values.Select(
|
|
|
+ session => session.Session).Where(
|
|
|
+ session => !session.Id.Equals(from.Id)).ToArray();
|
|
|
+ case SyncPlayBroadcastType.AllReady:
|
|
|
+ return _group.Participants.Values.Where(
|
|
|
+ session => !session.IsBuffering).Select(
|
|
|
+ session => session.Session).ToArray();
|
|
|
default:
|
|
|
return Array.Empty<SessionInfo>();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Sends a GroupUpdate message to the interested sessions.
|
|
|
- /// </summary>
|
|
|
- /// <param name="from">The current session.</param>
|
|
|
- /// <param name="type">The filtering type.</param>
|
|
|
- /// <param name="message">The message to send.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- /// <value>The task.</value>
|
|
|
- private Task SendGroupUpdate<T>(SessionInfo from, BroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken)
|
|
|
+ /// <inheritdoc />
|
|
|
+ public Task SendGroupUpdate<T>(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken)
|
|
|
{
|
|
|
IEnumerable<Task> GetTasks()
|
|
|
{
|
|
@@ -141,15 +125,8 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
return Task.WhenAll(GetTasks());
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Sends a playback command to the interested sessions.
|
|
|
- /// </summary>
|
|
|
- /// <param name="from">The current session.</param>
|
|
|
- /// <param name="type">The filtering type.</param>
|
|
|
- /// <param name="message">The message to send.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- /// <value>The task.</value>
|
|
|
- private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken)
|
|
|
+ /// <inheritdoc />
|
|
|
+ public Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken cancellationToken)
|
|
|
{
|
|
|
IEnumerable<Task> GetTasks()
|
|
|
{
|
|
@@ -162,12 +139,8 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
return Task.WhenAll(GetTasks());
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Builds a new playback command with some default values.
|
|
|
- /// </summary>
|
|
|
- /// <param name="type">The command type.</param>
|
|
|
- /// <value>The SendCommand.</value>
|
|
|
- private SendCommand NewSyncPlayCommand(SendCommandType type)
|
|
|
+ /// <inheritdoc />
|
|
|
+ public SendCommand NewSyncPlayCommand(SendCommandType type)
|
|
|
{
|
|
|
return new SendCommand()
|
|
|
{
|
|
@@ -179,13 +152,8 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Builds a new group update message.
|
|
|
- /// </summary>
|
|
|
- /// <param name="type">The update type.</param>
|
|
|
- /// <param name="data">The data to send.</param>
|
|
|
- /// <value>The GroupUpdate.</value>
|
|
|
- private GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data)
|
|
|
+ /// <inheritdoc />
|
|
|
+ public GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data)
|
|
|
{
|
|
|
return new GroupUpdate<T>()
|
|
|
{
|
|
@@ -195,19 +163,45 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
|
+ public string DateToUTCString(DateTime _date)
|
|
|
+ {
|
|
|
+ return _date.ToUniversalTime().ToString("o");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc />
|
|
|
+ public long SanitizePositionTicks(long? positionTicks)
|
|
|
+ {
|
|
|
+ var ticks = positionTicks ?? 0;
|
|
|
+ ticks = ticks >= 0 ? ticks : 0;
|
|
|
+ if (_group.PlayingItem != null)
|
|
|
+ {
|
|
|
+ var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0;
|
|
|
+ ticks = ticks > runTimeTicks ? runTimeTicks : ticks;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ticks;
|
|
|
+ }
|
|
|
+
|
|
|
/// <inheritdoc />
|
|
|
public void CreateGroup(SessionInfo session, CancellationToken cancellationToken)
|
|
|
{
|
|
|
_group.AddSession(session);
|
|
|
_syncPlayManager.AddSessionToGroup(session, this);
|
|
|
|
|
|
+ State = new PausedGroupState();
|
|
|
+
|
|
|
_group.PlayingItem = session.FullNowPlayingItem;
|
|
|
- _group.IsPaused = session.PlayState.IsPaused;
|
|
|
+ // TODO: looks like new groups should mantain playstate (and not force to pause)
|
|
|
+ // _group.IsPaused = session.PlayState.IsPaused;
|
|
|
_group.PositionTicks = session.PlayState.PositionTicks ?? 0;
|
|
|
_group.LastActivity = DateTime.UtcNow;
|
|
|
|
|
|
var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow));
|
|
|
- SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
|
|
|
+ SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
|
|
|
+ // TODO: looks like new groups should mantain playstate (and not force to pause)
|
|
|
+ var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause);
|
|
|
+ SendCommand(session, SyncPlayBroadcastType.CurrentSession, pauseCommand, cancellationToken);
|
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
@@ -219,21 +213,21 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
_syncPlayManager.AddSessionToGroup(session, this);
|
|
|
|
|
|
var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow));
|
|
|
- SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
|
|
|
+ SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
|
|
|
|
|
|
var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
|
|
|
- SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
|
|
|
+ SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
|
|
|
|
|
|
// Syncing will happen client-side
|
|
|
- if (!_group.IsPaused)
|
|
|
+ if (State.GetGroupState().Equals(GroupState.Playing))
|
|
|
{
|
|
|
var playCommand = NewSyncPlayCommand(SendCommandType.Play);
|
|
|
- SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken);
|
|
|
+ SendCommand(session, SyncPlayBroadcastType.CurrentSession, playCommand, cancellationToken);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause);
|
|
|
- SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
|
|
|
+ SendCommand(session, SyncPlayBroadcastType.CurrentSession, pauseCommand, cancellationToken);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
@@ -244,7 +238,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
StartPositionTicks = _group.PositionTicks
|
|
|
};
|
|
|
var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest);
|
|
|
- SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken);
|
|
|
+ SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -255,247 +249,21 @@ namespace Emby.Server.Implementations.SyncPlay
|
|
|
_syncPlayManager.RemoveSessionFromGroup(session, this);
|
|
|
|
|
|
var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks);
|
|
|
- SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
|
|
|
+ SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
|
|
|
|
|
|
var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName);
|
|
|
- SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
|
|
|
+ SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
|
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
- public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
|
|
|
+ public void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken)
|
|
|
{
|
|
|
// The server's job is to maintain a consistent state for clients to reference
|
|
|
// and notify clients of state changes. The actual syncing of media playback
|
|
|
// happens client side. Clients are aware of the server's time and use it to sync.
|
|
|
- switch (request.Type)
|
|
|
- {
|
|
|
- case PlaybackRequestType.Play:
|
|
|
- HandlePlayRequest(session, request, cancellationToken);
|
|
|
- break;
|
|
|
- case PlaybackRequestType.Pause:
|
|
|
- HandlePauseRequest(session, request, cancellationToken);
|
|
|
- break;
|
|
|
- case PlaybackRequestType.Seek:
|
|
|
- HandleSeekRequest(session, request, cancellationToken);
|
|
|
- break;
|
|
|
- case PlaybackRequestType.Buffer:
|
|
|
- HandleBufferingRequest(session, request, cancellationToken);
|
|
|
- break;
|
|
|
- case PlaybackRequestType.Ready:
|
|
|
- HandleBufferingDoneRequest(session, request, cancellationToken);
|
|
|
- break;
|
|
|
- case PlaybackRequestType.Ping:
|
|
|
- HandlePingUpdateRequest(session, request);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Handles a play action requested by a session.
|
|
|
- /// </summary>
|
|
|
- /// <param name="session">The session.</param>
|
|
|
- /// <param name="request">The play action.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- if (_group.IsPaused)
|
|
|
- {
|
|
|
- // Pick a suitable time that accounts for latency
|
|
|
- var delay = Math.Max(_group.GetHighestPing() * 2, GroupInfo.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
|
|
|
- _group.IsPaused = false;
|
|
|
- _group.LastActivity = DateTime.UtcNow.AddMilliseconds(
|
|
|
- delay);
|
|
|
-
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Play);
|
|
|
- SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Client got lost, sending current state
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Play);
|
|
|
- SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Handles a pause action requested by a session.
|
|
|
- /// </summary>
|
|
|
- /// <param name="session">The session.</param>
|
|
|
- /// <param name="request">The pause action.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- if (!_group.IsPaused)
|
|
|
- {
|
|
|
- // Pause group and compute the media playback position
|
|
|
- _group.IsPaused = true;
|
|
|
- var currentTime = DateTime.UtcNow;
|
|
|
- var elapsedTime = currentTime - _group.LastActivity;
|
|
|
- _group.LastActivity = currentTime;
|
|
|
-
|
|
|
- // Seek only if playback actually started
|
|
|
- // Pause request may be issued during the delay added to account for latency
|
|
|
- _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0;
|
|
|
-
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Pause);
|
|
|
- SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Client got lost, sending current state
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Pause);
|
|
|
- SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Handles a seek action requested by a session.
|
|
|
- /// </summary>
|
|
|
- /// <param name="session">The session.</param>
|
|
|
- /// <param name="request">The seek action.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- // Sanitize PositionTicks
|
|
|
- var ticks = SanitizePositionTicks(request.PositionTicks);
|
|
|
-
|
|
|
- // Pause and seek
|
|
|
- _group.IsPaused = true;
|
|
|
- _group.PositionTicks = ticks;
|
|
|
- _group.LastActivity = DateTime.UtcNow;
|
|
|
-
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Seek);
|
|
|
- SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Handles a buffering action requested by a session.
|
|
|
- /// </summary>
|
|
|
- /// <param name="session">The session.</param>
|
|
|
- /// <param name="request">The buffering action.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- if (!_group.IsPaused)
|
|
|
- {
|
|
|
- // Pause group and compute the media playback position
|
|
|
- _group.IsPaused = true;
|
|
|
- var currentTime = DateTime.UtcNow;
|
|
|
- var elapsedTime = currentTime - _group.LastActivity;
|
|
|
- _group.LastActivity = currentTime;
|
|
|
- _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0;
|
|
|
-
|
|
|
- _group.SetBuffering(session, true);
|
|
|
-
|
|
|
- // Send pause command to all non-buffering sessions
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Pause);
|
|
|
- SendCommand(session, BroadcastType.AllReady, command, cancellationToken);
|
|
|
-
|
|
|
- var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.GroupWait, session.UserName);
|
|
|
- SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Client got lost, sending current state
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Pause);
|
|
|
- SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Handles a buffering-done action requested by a session.
|
|
|
- /// </summary>
|
|
|
- /// <param name="session">The session.</param>
|
|
|
- /// <param name="request">The buffering-done action.</param>
|
|
|
- /// <param name="cancellationToken">The cancellation token.</param>
|
|
|
- private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- if (_group.IsPaused)
|
|
|
- {
|
|
|
- _group.SetBuffering(session, false);
|
|
|
-
|
|
|
- var requestTicks = SanitizePositionTicks(request.PositionTicks);
|
|
|
-
|
|
|
- var when = request.When ?? DateTime.UtcNow;
|
|
|
- var currentTime = DateTime.UtcNow;
|
|
|
- var elapsedTime = currentTime - when;
|
|
|
- var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime;
|
|
|
- var delay = _group.PositionTicks - clientPosition.Ticks;
|
|
|
-
|
|
|
- if (_group.IsBuffering())
|
|
|
- {
|
|
|
- // Others are still buffering, tell this client to pause when ready
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Pause);
|
|
|
- var pauseAtTime = currentTime.AddMilliseconds(delay);
|
|
|
- command.When = DateToUTCString(pauseAtTime);
|
|
|
- SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Let other clients resume as soon as the buffering client catches up
|
|
|
- _group.IsPaused = false;
|
|
|
-
|
|
|
- if (delay > _group.GetHighestPing() * 2)
|
|
|
- {
|
|
|
- // Client that was buffering is recovering, notifying others to resume
|
|
|
- _group.LastActivity = currentTime.AddMilliseconds(
|
|
|
- delay);
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Play);
|
|
|
- SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Client, that was buffering, resumed playback but did not update others in time
|
|
|
- delay = Math.Max(_group.GetHighestPing() * 2, GroupInfo.DefaultPing);
|
|
|
-
|
|
|
- _group.LastActivity = currentTime.AddMilliseconds(
|
|
|
- delay);
|
|
|
-
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Play);
|
|
|
- SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Group was not waiting, make sure client has latest state
|
|
|
- var command = NewSyncPlayCommand(SendCommandType.Play);
|
|
|
- SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Sanitizes the PositionTicks, considers the current playing item when available.
|
|
|
- /// </summary>
|
|
|
- /// <param name="positionTicks">The PositionTicks.</param>
|
|
|
- /// <value>The sanitized PositionTicks.</value>
|
|
|
- private long SanitizePositionTicks(long? positionTicks)
|
|
|
- {
|
|
|
- var ticks = positionTicks ?? 0;
|
|
|
- ticks = ticks >= 0 ? ticks : 0;
|
|
|
- if (_group.PlayingItem != null)
|
|
|
- {
|
|
|
- var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0;
|
|
|
- ticks = ticks > runTimeTicks ? runTimeTicks : ticks;
|
|
|
- }
|
|
|
-
|
|
|
- return ticks;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Updates ping of a session.
|
|
|
- /// </summary>
|
|
|
- /// <param name="session">The session.</param>
|
|
|
- /// <param name="request">The update.</param>
|
|
|
- private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
|
|
|
- {
|
|
|
- // Collected pings are used to account for network latency when unpausing playback
|
|
|
- _group.UpdatePing(session, request.Ping ?? GroupInfo.DefaultPing);
|
|
|
+ _logger.LogInformation("HandleRequest: {0}:{1}.", request.GetType(), State.GetGroupState());
|
|
|
+ _ = request.Apply(this, State, session, cancellationToken);
|
|
|
+ // TODO: do something with returned value
|
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|