From e10799e0e8d7afcdf07585ac4b27dd060c973d8f Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 12 May 2020 19:05:05 +0200 Subject: Rewrite syncplay using a state design pattern --- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 6 -- .../SyncPlay/IPlaybackGroupRequest.cs | 24 ++++++ .../SyncPlay/ISyncPlayController.cs | 2 +- .../SyncPlay/ISyncPlayManager.cs | 2 +- MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs | 95 ++++++++++++++++++++++ .../SyncPlay/ISyncPlayStateContext.cs | 75 +++++++++++++++++ .../SyncPlay/PlaybackRequest/BufferGroupRequest.cs | 43 ++++++++++ .../SyncPlay/PlaybackRequest/PauseGroupRequest.cs | 24 ++++++ .../SyncPlay/PlaybackRequest/PingGroupRequest.cs | 31 +++++++ .../SyncPlay/PlaybackRequest/PlayGroupRequest.cs | 24 ++++++ .../SyncPlay/PlaybackRequest/ReadyGroupRequest.cs | 43 ++++++++++ .../SyncPlay/PlaybackRequest/SeekGroupRequest.cs | 30 +++++++ .../SyncPlay/SyncPlayAbstractState.cs | 65 +++++++++++++++ 13 files changed, 456 insertions(+), 8 deletions(-) create mode 100644 MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs create mode 100644 MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index a1cada25cc..cdd24d0b59 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -30,12 +30,6 @@ namespace MediaBrowser.Controller.SyncPlay /// The playing item. public BaseItem PlayingItem { get; set; } - /// - /// Gets or sets a value indicating whether playback is paused. - /// - /// Playback is paused. - public bool IsPaused { get; set; } - /// /// Gets or sets a value indicating whether there are position ticks. /// diff --git a/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs new file mode 100644 index 0000000000..a6e87a007f --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs @@ -0,0 +1,24 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Interface IPlaybackGroupRequest. + /// + public interface IPlaybackGroupRequest + { + /// + /// Gets the playback request type. + /// + /// The playback request type. + PlaybackRequestType Type(); + + /// + /// Applies the request to a group. + /// + /// The operation completion status. + bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs index d869c05bd1..5ac2aeb247 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The session. /// The requested action. /// The cancellation token. - void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); + void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken); /// /// Gets the info about the group for the clients. diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 65770021dc..6fa94e2ce4 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The session. /// The request. /// The cancellation token. - void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); + void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken); /// /// Maps a session to a group. diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs new file mode 100644 index 0000000000..55c9ee938f --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs @@ -0,0 +1,95 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Interface ISyncPlayState. + /// + public interface ISyncPlayState + { + /// + /// Gets the group state. + /// + /// The group state. + GroupState GetGroupState(); + + /// + /// Generic handle. Context's state can change. + /// + /// The context of the state. + /// Whether the state has been just set. + /// The play action. + /// The session. + /// The cancellation token. + /// The operation completion status. + bool HandleRequest(ISyncPlayStateContext context, bool newState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a play action requested by a session. Context's state can change. + /// + /// The context of the state. + /// Whether the state has been just set. + /// The play action. + /// The session. + /// The cancellation token. + /// The operation completion status. + bool HandleRequest(ISyncPlayStateContext context, bool newState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a pause action requested by a session. Context's state can change. + /// + /// The context of the state. + /// Whether the state has been just set. + /// The pause action. + /// The session. + /// The cancellation token. + /// The operation completion status. + bool HandleRequest(ISyncPlayStateContext context, bool newState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a seek action requested by a session. Context's state can change. + /// + /// The context of the state. + /// Whether the state has been just set. + /// The seek action. + /// The session. + /// The cancellation token. + /// The operation completion status. + bool HandleRequest(ISyncPlayStateContext context, bool newState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a buffering action requested by a session. Context's state can change. + /// + /// The context of the state. + /// Whether the state has been just set. + /// The buffering action. + /// The session. + /// The cancellation token. + /// The operation completion status. + bool HandleRequest(ISyncPlayStateContext context, bool newState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles a buffering-done action requested by a session. Context's state can change. + /// + /// The context of the state. + /// Whether the state has been just set. + /// The buffering-done action. + /// The session. + /// The cancellation token. + /// The operation completion status. + bool HandleRequest(ISyncPlayStateContext context, bool newState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// + /// Updates ping of a session. Context's state should not change. + /// + /// The context of the state. + /// Whether the state has been just set. + /// The buffering-done action. + /// The session. + /// The cancellation token. + /// The operation completion status. + bool HandleRequest(ISyncPlayStateContext context, bool newState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs new file mode 100644 index 0000000000..9bdb1ace6c --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Interface ISyncPlayStateContext. + /// + public interface ISyncPlayStateContext + { + /// + /// Gets the context's group. + /// + /// The group. + GroupInfo GetGroup(); + + /// + /// Sets a new state. + /// + /// The new state. + void SetState(ISyncPlayState state); + + /// + /// Sends a GroupUpdate message to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The cancellation token. + /// The task. + Task SendGroupUpdate(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate message, CancellationToken cancellationToken); + + /// + /// Sends a playback command to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The cancellation token. + /// The task. + Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken cancellationToken); + + /// + /// Builds a new playback command with some default values. + /// + /// The command type. + /// The SendCommand. + SendCommand NewSyncPlayCommand(SendCommandType type); + + /// + /// Builds a new group update message. + /// + /// The update type. + /// The data to send. + /// The GroupUpdate. + GroupUpdate NewSyncPlayGroupUpdate(GroupUpdateType type, T data); + + /// + /// Converts DateTime to UTC string. + /// + /// The date to convert. + /// The UTC string. + string DateToUTCString(DateTime date); + + /// + /// Sanitizes the PositionTicks, considers the current playing item when available. + /// + /// The PositionTicks. + /// The sanitized PositionTicks. + long SanitizePositionTicks(long? positionTicks); + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs new file mode 100644 index 0000000000..21dae8e4e6 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class BufferingGroupRequest. + /// + public class BufferGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public Guid PlayingItemId { get; set; } + + /// + public PlaybackRequestType Type() + { + return PlaybackRequestType.Buffer; + } + + /// + public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + return state.HandleRequest(context, false, this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs new file mode 100644 index 0000000000..21a46add8c --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs @@ -0,0 +1,24 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class PauseGroupRequest. + /// + public class PauseGroupRequest : IPlaybackGroupRequest + { + /// + public PlaybackRequestType Type() + { + return PlaybackRequestType.Pause; + } + + /// + public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + return state.HandleRequest(context, false, this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs new file mode 100644 index 0000000000..2f78edfc56 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs @@ -0,0 +1,31 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +// FIXME: not really group related, can be moved up to SyncPlayController maybe? +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class UpdatePingGroupRequest. + /// + public class PingGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the ping time. + /// + /// The ping time. + public long Ping { get; set; } + + /// + public PlaybackRequestType Type() + { + return PlaybackRequestType.Ping; + } + + /// + public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + return state.HandleRequest(context, false, this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs new file mode 100644 index 0000000000..942229a775 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs @@ -0,0 +1,24 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class PlayGroupRequest. + /// + public class PlayGroupRequest : IPlaybackGroupRequest + { + /// + public PlaybackRequestType Type() + { + return PlaybackRequestType.Play; + } + + /// + public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + return state.HandleRequest(context, false, this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs new file mode 100644 index 0000000000..ee88ddddbb --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class BufferingDoneGroupRequest. + /// + public class ReadyGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public Guid PlayingItemId { get; set; } + + /// + public PlaybackRequestType Type() + { + return PlaybackRequestType.Ready; + } + + /// + public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + return state.HandleRequest(context, false, this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs new file mode 100644 index 0000000000..bb5e7a343e --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class SeekGroupRequest. + /// + public class SeekGroupRequest : IPlaybackGroupRequest + { + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + public PlaybackRequestType Type() + { + return PlaybackRequestType.Seek; + } + + /// + public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + return state.HandleRequest(context, false, this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs b/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs new file mode 100644 index 0000000000..0b72d16686 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs @@ -0,0 +1,65 @@ +using System.Threading; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.SyncPlay; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class SyncPlayAbstractState. + /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// + public abstract class SyncPlayAbstractState : ISyncPlayState + { + /// + public abstract GroupState GetGroupState(); + + /// + public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + return true; + } + + /// + public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + return true; + } + + /// + public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + return true; + } + + /// + public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + return true; + } + + /// + public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + return true; + } + + /// + public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + return true; + } + + /// + public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + GroupInfo group = context.GetGroup(); + + // Collected pings are used to account for network latency when unpausing playback + group.UpdatePing(session, request.Ping); + + return true; + } + } +} -- cgit v1.2.3