aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgion <oancaionutandrei@gmail.com>2020-05-04 19:46:02 +0200
committergion <oancaionutandrei@gmail.com>2020-05-09 12:37:23 +0200
commit6e22e9222b68ad117550c02a8cbce2d65878f50b (patch)
treec5654f590a3aecf9a4691f25beb5e41d57442ede
parent0b974d09ca08f70d9cd61d4871698956026b7b3b (diff)
Fix code issues
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs4
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs127
-rw-r--r--Emby.Server.Implementations/Syncplay/SyncplayController.cs158
-rw-r--r--Emby.Server.Implementations/Syncplay/SyncplayManager.cs61
-rw-r--r--MediaBrowser.Api/Syncplay/SyncplayService.cs85
-rw-r--r--MediaBrowser.Api/Syncplay/TimeSyncService.cs13
-rw-r--r--MediaBrowser.Controller/Syncplay/GroupInfo.cs8
-rw-r--r--MediaBrowser.Controller/Syncplay/ISyncplayController.cs13
-rw-r--r--MediaBrowser.Controller/Syncplay/ISyncplayManager.cs16
-rw-r--r--MediaBrowser.Model/Syncplay/GroupUpdateType.cs6
-rw-r--r--MediaBrowser.Model/Syncplay/PlaybackRequest.cs2
11 files changed, 281 insertions, 212 deletions
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index c819c163a..4c33ff71b 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -238,10 +238,10 @@ namespace Emby.Server.Implementations.HttpServer
return _socket.SendAsync(text, true, cancellationToken);
}
- private Task SendKeepAliveResponse()
+ private void SendKeepAliveResponse()
{
LastKeepAliveDate = DateTime.UtcNow;
- return SendAsync(new WebSocketMessage<string>
+ SendAsync(new WebSocketMessage<string>
{
MessageType = "KeepAlive"
}, CancellationToken.None);
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index 7a316b070..d1ee22ea8 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -84,10 +84,10 @@ namespace Emby.Server.Implementations.Session
_logger = loggerFactory.CreateLogger(GetType().Name);
_json = json;
_httpServer = httpServer;
- httpServer.WebSocketConnected += _serverManager_WebSocketConnected;
+ httpServer.WebSocketConnected += OnServerManagerWebSocketConnected;
}
- void _serverManager_WebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e)
+ void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e)
{
var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint);
@@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Session
public void Dispose()
{
- _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected;
+ _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected;
StopKeepAlive();
}
@@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Session
private void OnWebSocketClosed(object sender, EventArgs e)
{
var webSocket = (IWebSocketConnection) sender;
- _logger.LogDebug("WebSockets {0} closed.", webSocket);
+ _logger.LogDebug("WebSocket {0} is closed.", webSocket);
RemoveWebSocket(webSocket);
}
@@ -157,7 +157,7 @@ namespace Emby.Server.Implementations.Session
/// Adds a WebSocket to the KeepAlive watchlist.
/// </summary>
/// <param name="webSocket">The WebSocket to monitor.</param>
- private async Task KeepAliveWebSocket(IWebSocketConnection webSocket)
+ private void KeepAliveWebSocket(IWebSocketConnection webSocket)
{
lock (_webSocketsLock)
{
@@ -175,11 +175,11 @@ namespace Emby.Server.Implementations.Session
// Notify WebSocket about timeout
try
{
- await SendForceKeepAlive(webSocket);
+ SendForceKeepAlive(webSocket).Wait();
}
catch (WebSocketException exception)
{
- _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket {0}.", webSocket);
+ _logger.LogWarning(exception, "Cannot send ForceKeepAlive message to WebSocket {0}.", webSocket);
}
}
@@ -213,7 +213,8 @@ namespace Emby.Server.Implementations.Session
{
_keepAliveCancellationToken = new CancellationTokenSource();
// Start KeepAlive watcher
- KeepAliveSockets(
+ var task = RepeatAsyncCallbackEvery(
+ KeepAliveSockets,
TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor),
_keepAliveCancellationToken.Token);
}
@@ -245,73 +246,58 @@ namespace Emby.Server.Implementations.Session
}
/// <summary>
- /// Checks status of KeepAlive of WebSockets once every the specified interval time.
+ /// Checks status of KeepAlive of WebSockets.
/// </summary>
- /// <param name="interval">The interval.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- private async Task KeepAliveSockets(TimeSpan interval, CancellationToken cancellationToken)
+ private async Task KeepAliveSockets()
{
- while (true)
+ IEnumerable<IWebSocketConnection> inactive;
+ IEnumerable<IWebSocketConnection> lost;
+
+ lock (_webSocketsLock)
{
- _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count());
+ _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count);
- IEnumerable<IWebSocketConnection> inactive;
- IEnumerable<IWebSocketConnection> lost;
- lock (_webSocketsLock)
+ inactive = _webSockets.Where(i =>
{
- inactive = _webSockets.Where(i =>
- {
- var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds;
- return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout);
- });
- lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout);
- }
+ var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds;
+ return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout);
+ });
+ lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout);
+ }
- if (inactive.Any())
+ if (inactive.Any())
+ {
+ _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count());
+ }
+
+ foreach (var webSocket in inactive)
+ {
+ try
{
- _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count());
+ await SendForceKeepAlive(webSocket);
}
-
- foreach (var webSocket in inactive)
+ catch (WebSocketException exception)
{
- try
- {
- await SendForceKeepAlive(webSocket);
- }
- catch (WebSocketException exception)
- {
- _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket.");
- lost = lost.Append(webSocket);
- }
+ _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket.");
+ lost = lost.Append(webSocket);
}
+ }
- lock (_webSocketsLock)
+ lock (_webSocketsLock)
+ {
+ if (lost.Any())
{
- if (lost.Any())
- {
- _logger.LogInformation("Lost {0} WebSockets.", lost.Count());
- foreach (var webSocket in lost.ToList())
- {
- // TODO: handle session relative to the lost webSocket
- RemoveWebSocket(webSocket);
- }
- }
-
- if (!_webSockets.Any())
+ _logger.LogInformation("Lost {0} WebSockets.", lost.Count());
+ foreach (var webSocket in lost.ToList())
{
- StopKeepAlive();
+ // TODO: handle session relative to the lost webSocket
+ RemoveWebSocket(webSocket);
}
}
- // Wait for next interval
- Task task = Task.Delay(interval, cancellationToken);
- try
+ if (!_webSockets.Any())
{
- await task;
- }
- catch (TaskCanceledException)
- {
- return;
+ StopKeepAlive();
}
}
}
@@ -329,5 +315,30 @@ namespace Emby.Server.Implementations.Session
Data = WebSocketLostTimeout
}, CancellationToken.None);
}
+
+ /// <summary>
+ /// Runs a given async callback once every specified interval time, until cancelled.
+ /// </summary>
+ /// <param name="callback">The async callback.</param>
+ /// <param name="interval">The interval time.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ private async Task RepeatAsyncCallbackEvery(Func<Task> callback, TimeSpan interval, CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await callback();
+ Task task = Task.Delay(interval, cancellationToken);
+
+ try
+ {
+ await task;
+ }
+ catch (TaskCanceledException)
+ {
+ return;
+ }
+ }
+ }
}
}
diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs
index 02cf08cd7..8cc3d1fac 100644
--- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs
+++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs
@@ -7,13 +7,15 @@ 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
{
/// <summary>
/// Class SyncplayController.
/// </summary>
+ /// <remarks>
+ /// Class is not thread-safe, external locking is required when accessing methods.
+ /// </remarks>
public class SyncplayController : ISyncplayController, IDisposable
{
/// <summary>
@@ -40,11 +42,6 @@ namespace Emby.Server.Implementations.Syncplay
}
/// <summary>
- /// The logger.
- /// </summary>
- private readonly ILogger _logger;
-
- /// <summary>
/// The session manager.
/// </summary>
private readonly ISessionManager _sessionManager;
@@ -71,11 +68,9 @@ namespace Emby.Server.Implementations.Syncplay
private bool _disposed = false;
public SyncplayController(
- ILogger logger,
ISessionManager sessionManager,
ISyncplayManager syncplayManager)
{
- _logger = logger;
_sessionManager = sessionManager;
_syncplayManager = syncplayManager;
}
@@ -111,6 +106,16 @@ namespace Emby.Server.Implementations.Syncplay
}
/// <summary>
+ /// Converts DateTime to UTC string.
+ /// </summary>
+ /// <param name="date">The date to convert.</param>
+ /// <value>The UTC string.</value>
+ private string DateToUTCString(DateTime date)
+ {
+ return date.ToUniversalTime().ToString("o");
+ }
+
+ /// <summary>
/// Filters sessions of this group.
/// </summary>
/// <param name="from">The current session.</param>
@@ -149,15 +154,16 @@ namespace Emby.Server.Implementations.Syncplay
/// <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)
+ private Task SendGroupUpdate<T>(SessionInfo from, BroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken)
{
IEnumerable<Task> GetTasks()
{
SessionInfo[] sessions = FilterSessions(from, type);
foreach (var session in sessions)
{
- yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, CancellationToken.None);
+ yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, cancellationToken);
}
}
@@ -170,15 +176,16 @@ namespace Emby.Server.Implementations.Syncplay
/// <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)
+ private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken)
{
IEnumerable<Task> GetTasks()
{
SessionInfo[] sessions = FilterSessions(from, type);
foreach (var session in sessions)
{
- yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, CancellationToken.None);
+ yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, cancellationToken);
}
}
@@ -197,8 +204,8 @@ namespace Emby.Server.Implementations.Syncplay
GroupId = _group.GroupId.ToString(),
Command = type,
PositionTicks = _group.PositionTicks,
- When = _group.LastActivity.ToUniversalTime().ToString("o"),
- EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o")
+ When = DateToUTCString(_group.LastActivity),
+ EmittedAt = DateToUTCString(DateTime.UtcNow)
};
}
@@ -219,46 +226,46 @@ namespace Emby.Server.Implementations.Syncplay
}
/// <inheritdoc />
- public void InitGroup(SessionInfo session)
+ public void InitGroup(SessionInfo session, CancellationToken cancellationToken)
{
_group.AddSession(session);
_syncplayManager.AddSessionToGroup(session, this);
_group.PlayingItem = session.FullNowPlayingItem;
_group.IsPaused = true;
- _group.PositionTicks = session.PlayState.PositionTicks ??= 0;
+ _group.PositionTicks = session.PlayState.PositionTicks ?? 0;
_group.LastActivity = DateTime.UtcNow;
- var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o"));
- SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession);
+ var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow));
+ SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
var pauseCommand = NewSyncplayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.CurrentSession, pauseCommand);
+ SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
}
/// <inheritdoc />
- public void SessionJoin(SessionInfo session, JoinGroupRequest request)
+ public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken)
{
if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id)
{
_group.AddSession(session);
_syncplayManager.AddSessionToGroup(session, this);
- var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o"));
- SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession);
+ var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow));
+ SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
- SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers);
+ SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
// Client join and play, syncing will happen client side
if (!_group.IsPaused)
{
var playCommand = NewSyncplayCommand(SendCommandType.Play);
- SendCommand(session, BroadcastType.CurrentSession, playCommand);
+ SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken);
}
else
{
var pauseCommand = NewSyncplayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.CurrentSession, pauseCommand);
+ SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
}
}
else
@@ -267,25 +274,25 @@ namespace Emby.Server.Implementations.Syncplay
playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id };
playRequest.StartPositionTicks = _group.PositionTicks;
var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest);
- SendGroupUpdate(session, BroadcastType.CurrentSession, update);
+ SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken);
}
}
/// <inheritdoc />
- public void SessionLeave(SessionInfo session)
+ public void SessionLeave(SessionInfo session, CancellationToken cancellationToken)
{
_group.RemoveSession(session);
_syncplayManager.RemoveSessionFromGroup(session, this);
var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks);
- SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession);
+ SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName);
- SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers);
+ SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
}
/// <inheritdoc />
- public void HandleRequest(SessionInfo session, PlaybackRequest request)
+ public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
// The server's job is to mantain a consistent state to which clients refer to,
// as also to notify clients of state changes.
@@ -294,19 +301,19 @@ namespace Emby.Server.Implementations.Syncplay
switch (request.Type)
{
case PlaybackRequestType.Play:
- HandlePlayRequest(session, request);
+ HandlePlayRequest(session, request, cancellationToken);
break;
case PlaybackRequestType.Pause:
- HandlePauseRequest(session, request);
+ HandlePauseRequest(session, request, cancellationToken);
break;
case PlaybackRequestType.Seek:
- HandleSeekRequest(session, request);
+ HandleSeekRequest(session, request, cancellationToken);
break;
case PlaybackRequestType.Buffering:
- HandleBufferingRequest(session, request);
+ HandleBufferingRequest(session, request, cancellationToken);
break;
case PlaybackRequestType.BufferingDone:
- HandleBufferingDoneRequest(session, request);
+ HandleBufferingDoneRequest(session, request, cancellationToken);
break;
case PlaybackRequestType.UpdatePing:
HandlePingUpdateRequest(session, request);
@@ -319,7 +326,8 @@ namespace Emby.Server.Implementations.Syncplay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="request">The play action.</param>
- private void HandlePlayRequest(SessionInfo session, PlaybackRequest request)
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
if (_group.IsPaused)
{
@@ -337,13 +345,13 @@ namespace Emby.Server.Implementations.Syncplay
);
var command = NewSyncplayCommand(SendCommandType.Play);
- SendCommand(session, BroadcastType.AllGroup, command);
+ SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
}
else
{
// Client got lost, sending current state
var command = NewSyncplayCommand(SendCommandType.Play);
- SendCommand(session, BroadcastType.CurrentSession, command);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
}
}
@@ -352,7 +360,8 @@ namespace Emby.Server.Implementations.Syncplay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="request">The pause action.</param>
- private void HandlePauseRequest(SessionInfo session, PlaybackRequest request)
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
if (!_group.IsPaused)
{
@@ -366,13 +375,13 @@ namespace Emby.Server.Implementations.Syncplay
_group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0;
var command = NewSyncplayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.AllGroup, command);
+ SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
}
else
{
// Client got lost, sending current state
var command = NewSyncplayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.CurrentSession, command);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
}
}
@@ -381,16 +390,11 @@ namespace Emby.Server.Implementations.Syncplay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="request">The seek action.</param>
- private void HandleSeekRequest(SessionInfo session, PlaybackRequest request)
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
// Sanitize PositionTicks
- var ticks = request.PositionTicks ??= 0;
- ticks = ticks >= 0 ? ticks : 0;
- if (_group.PlayingItem.RunTimeTicks != null)
- {
- var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0;
- ticks = ticks > runTimeTicks ? runTimeTicks : ticks;
- }
+ var ticks = SanitizePositionTicks(request.PositionTicks);
// Pause and seek
_group.IsPaused = true;
@@ -398,7 +402,7 @@ namespace Emby.Server.Implementations.Syncplay
_group.LastActivity = DateTime.UtcNow;
var command = NewSyncplayCommand(SendCommandType.Seek);
- SendCommand(session, BroadcastType.AllGroup, command);
+ SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
}
/// <summary>
@@ -406,7 +410,8 @@ namespace Emby.Server.Implementations.Syncplay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="request">The buffering action.</param>
- private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request)
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
if (!_group.IsPaused)
{
@@ -421,16 +426,16 @@ namespace Emby.Server.Implementations.Syncplay
// Send pause command to all non-buffering sessions
var command = NewSyncplayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.AllReady, command);
+ SendCommand(session, BroadcastType.AllReady, command, cancellationToken);
var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName);
- SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers);
+ SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
}
else
{
// Client got lost, sending current state
var command = NewSyncplayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.CurrentSession, command);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
}
}
@@ -439,26 +444,28 @@ namespace Emby.Server.Implementations.Syncplay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="request">The buffering-done action.</param>
- private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request)
+ /// <param name="cancellationToken">The cancellation token.</param>
+ private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
if (_group.IsPaused)
{
_group.SetBuffering(session, false);
- var when = request.When ??= DateTime.UtcNow;
+ var requestTicks = SanitizePositionTicks(request.PositionTicks);
+
+ var when = request.When ?? DateTime.UtcNow;
var currentTime = DateTime.UtcNow;
var elapsedTime = currentTime - when;
- var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime;
+ var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime;
var delay = _group.PositionTicks - clientPosition.Ticks;
if (_group.IsBuffering())
{
- // Others are buffering, tell this client to pause when ready
+ // Others are still buffering, tell this client to pause when ready
var command = NewSyncplayCommand(SendCommandType.Pause);
- command.When = currentTime.AddMilliseconds(
- delay
- ).ToUniversalTime().ToString("o");
- SendCommand(session, BroadcastType.CurrentSession, command);
+ var pauseAtTime = currentTime.AddMilliseconds(delay);
+ command.When = DateToUTCString(pauseAtTime);
+ SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken);
}
else
{
@@ -472,7 +479,7 @@ namespace Emby.Server.Implementations.Syncplay
delay
);
var command = NewSyncplayCommand(SendCommandType.Play);
- SendCommand(session, BroadcastType.AllExceptCurrentSession, command);
+ SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken);
}
else
{
@@ -485,7 +492,7 @@ namespace Emby.Server.Implementations.Syncplay
);
var command = NewSyncplayCommand(SendCommandType.Play);
- SendCommand(session, BroadcastType.AllGroup, command);
+ SendCommand(session, BroadcastType.AllGroup, command, cancellationToken);
}
}
}
@@ -493,8 +500,25 @@ namespace Emby.Server.Implementations.Syncplay
{
// Group was not waiting, make sure client has latest state
var command = NewSyncplayCommand(SendCommandType.Play);
- SendCommand(session, BroadcastType.CurrentSession, command);
+ 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>
@@ -505,7 +529,7 @@ namespace Emby.Server.Implementations.Syncplay
private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
{
// Collected pings are used to account for network latency when unpausing playback
- _group.UpdatePing(session, request.Ping ??= _group.DefaulPing);
+ _group.UpdatePing(session, request.Ping ?? _group.DefaulPing);
}
/// <inheritdoc />
@@ -517,7 +541,7 @@ namespace Emby.Server.Implementations.Syncplay
PlayingItemName = _group.PlayingItem.Name,
PlayingItemId = _group.PlayingItem.Id.ToString(),
PositionTicks = _group.PositionTicks,
- Participants = _group.Participants.Values.Select(session => session.Session.UserName).ToArray()
+ Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray()
};
}
}
diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs
index eb61da7f3..7074e2225 100644
--- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs
+++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs
@@ -114,14 +114,14 @@ namespace Emby.Server.Implementations.Syncplay
{
var session = e.SessionInfo;
if (!IsSessionInGroup(session)) return;
- LeaveGroup(session);
+ LeaveGroup(session, CancellationToken.None);
}
private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e)
{
var session = e.Session;
if (!IsSessionInGroup(session)) return;
- LeaveGroup(session);
+ LeaveGroup(session, CancellationToken.None);
}
private bool IsSessionInGroup(SessionInfo session)
@@ -132,7 +132,13 @@ namespace Emby.Server.Implementations.Syncplay
private bool HasAccessToItem(User user, Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
- var hasParentalRatingAccess = user.Policy.MaxParentalRating.HasValue ? item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating : true;
+
+ // Check ParentalRating access
+ var hasParentalRatingAccess = true;
+ if (user.Policy.MaxParentalRating.HasValue)
+ {
+ hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating;
+ }
if (!user.Policy.EnableAllFolders && hasParentalRatingAccess)
{
@@ -140,7 +146,7 @@ namespace Emby.Server.Implementations.Syncplay
folder => folder.Id.ToString("N", CultureInfo.InvariantCulture)
);
var intersect = collections.Intersect(user.Policy.EnabledFolders);
- return intersect.Count() > 0;
+ return intersect.Any();
}
else
{
@@ -163,13 +169,13 @@ namespace Emby.Server.Implementations.Syncplay
}
/// <inheritdoc />
- public void NewGroup(SessionInfo session)
+ public void NewGroup(SessionInfo session, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(session.UserId);
if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups)
{
- _logger.LogWarning("Syncplaymanager NewGroup: {0} does not have permission to create groups.", session.Id);
+ _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id);
var error = new GroupUpdate<string>()
{
@@ -183,24 +189,24 @@ namespace Emby.Server.Implementations.Syncplay
{
if (IsSessionInGroup(session))
{
- LeaveGroup(session);
+ LeaveGroup(session, cancellationToken);
}
- var group = new SyncplayController(_logger, _sessionManager, this);
+ var group = new SyncplayController(_sessionManager, this);
_groups[group.GetGroupId().ToString()] = group;
- group.InitGroup(session);
+ group.InitGroup(session, cancellationToken);
}
}
/// <inheritdoc />
- public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request)
+ public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(session.UserId);
if (user.Policy.SyncplayAccess == SyncplayAccess.None)
{
- _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to Syncplay.", session.Id);
+ _logger.LogWarning("JoinGroup: {0} does not have access to Syncplay.", session.Id);
var error = new GroupUpdate<string>()
{
@@ -217,11 +223,11 @@ namespace Emby.Server.Implementations.Syncplay
if (group == null)
{
- _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId);
+ _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId);
var error = new GroupUpdate<string>()
{
- Type = GroupUpdateType.GroupNotJoined
+ Type = GroupUpdateType.GroupDoesNotExist
};
_sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None);
return;
@@ -229,7 +235,7 @@ namespace Emby.Server.Implementations.Syncplay
if (!HasAccessToItem(user, group.GetPlayingItemId()))
{
- _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId());
+ _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId());
var error = new GroupUpdate<string>()
{
@@ -243,15 +249,15 @@ namespace Emby.Server.Implementations.Syncplay
if (IsSessionInGroup(session))
{
if (GetSessionGroup(session).Equals(groupId)) return;
- LeaveGroup(session);
+ LeaveGroup(session, cancellationToken);
}
- group.SessionJoin(session, request);
+ group.SessionJoin(session, request, cancellationToken);
}
}
/// <inheritdoc />
- public void LeaveGroup(SessionInfo session)
+ public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken)
{
// TODO: determine what happens to users that are in a group and get their permissions revoked
lock (_groupsLock)
@@ -261,7 +267,7 @@ namespace Emby.Server.Implementations.Syncplay
if (group == null)
{
- _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id);
+ _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id);
var error = new GroupUpdate<string>()
{
@@ -271,17 +277,18 @@ namespace Emby.Server.Implementations.Syncplay
return;
}
- group.SessionLeave(session);
+ group.SessionLeave(session, cancellationToken);
if (group.IsGroupEmpty())
{
+ _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId());
_groups.Remove(group.GetGroupId().ToString(), out _);
}
}
}
/// <inheritdoc />
- public List<GroupInfoView> ListGroups(SessionInfo session)
+ public List<GroupInfoView> ListGroups(SessionInfo session, Guid filterItemId)
{
var user = _userManager.GetUserById(session.UserId);
@@ -290,11 +297,11 @@ namespace Emby.Server.Implementations.Syncplay
return new List<GroupInfoView>();
}
- // Filter by playing item if the user is viewing something already
- if (session.NowPlayingItem != null)
+ // Filter by item if requested
+ if (!filterItemId.Equals(Guid.Empty))
{
return _groups.Values.Where(
- group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) && HasAccessToItem(user, group.GetPlayingItemId())
+ group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())
).Select(
group => group.GetInfo()
).ToList();
@@ -311,13 +318,13 @@ namespace Emby.Server.Implementations.Syncplay
}
/// <inheritdoc />
- public void HandleRequest(SessionInfo session, PlaybackRequest request)
+ public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(session.UserId);
if (user.Policy.SyncplayAccess == SyncplayAccess.None)
{
- _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not have access to Syncplay.", session.Id);
+ _logger.LogWarning("HandleRequest: {0} does not have access to Syncplay.", session.Id);
var error = new GroupUpdate<string>()
{
@@ -334,7 +341,7 @@ namespace Emby.Server.Implementations.Syncplay
if (group == null)
{
- _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id);
+ _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id);
var error = new GroupUpdate<string>()
{
@@ -344,7 +351,7 @@ namespace Emby.Server.Implementations.Syncplay
return;
}
- group.HandleRequest(session, request);
+ group.HandleRequest(session, request, cancellationToken);
}
}
diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs
index 2eaf9ce83..4b6e16762 100644
--- a/MediaBrowser.Api/Syncplay/SyncplayService.cs
+++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs
@@ -1,3 +1,4 @@
+using System.Threading;
using System;
using System.Collections.Generic;
using MediaBrowser.Controller.Configuration;
@@ -48,12 +49,19 @@ namespace MediaBrowser.Api.Syncplay
public string SessionId { get; set; }
}
- [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups playing same item")]
+ [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups")]
[Authenticated]
public class SyncplayListGroups : IReturnVoid
{
[ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string SessionId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the filter item id.
+ /// </summary>
+ /// <value>The filter item id.</value>
+ [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string FilterItemId { get; set; }
}
[Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")]
@@ -104,8 +112,8 @@ namespace MediaBrowser.Api.Syncplay
/// Gets or sets whether this is a buffering or a buffering-done request.
/// </summary>
/// <value><c>true</c> if buffering is complete; <c>false</c> otherwise.</value>
- [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
- public bool Resume { get; set; }
+ [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
+ public bool BufferingDone { get; set; }
}
[Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")]
@@ -125,11 +133,6 @@ namespace MediaBrowser.Api.Syncplay
public class SyncplayService : BaseApiService
{
/// <summary>
- /// The session manager.
- /// </summary>
- private readonly ISessionManager _sessionManager;
-
- /// <summary>
/// The session context.
/// </summary>
private readonly ISessionContext _sessionContext;
@@ -143,12 +146,10 @@ namespace MediaBrowser.Api.Syncplay
ILogger<SyncplayService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
- ISessionManager sessionManager,
ISessionContext sessionContext,
ISyncplayManager syncplayManager)
: base(logger, serverConfigurationManager, httpResultFactory)
{
- _sessionManager = sessionManager;
_sessionContext = sessionContext;
_syncplayManager = syncplayManager;
}
@@ -160,7 +161,7 @@ namespace MediaBrowser.Api.Syncplay
public void Post(SyncplayNewGroup request)
{
var currentSession = GetSession(_sessionContext);
- _syncplayManager.NewGroup(currentSession);
+ _syncplayManager.NewGroup(currentSession, CancellationToken.None);
}
/// <summary>
@@ -174,19 +175,27 @@ namespace MediaBrowser.Api.Syncplay
{
GroupId = Guid.Parse(request.GroupId)
};
- try
- {
- joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId);
- }
- catch (ArgumentNullException)
- {
- // Do nothing
- }
- catch (FormatException)
+
+ // Both null and empty strings mean that client isn't playing anything
+ if (!String.IsNullOrEmpty(request.PlayingItemId))
{
- // Do nothing
+ try
+ {
+ joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId);
+ }
+ catch (ArgumentNullException)
+ {
+ // Should never happen, but just in case
+ Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request.");
+ return;
+ }
+ catch (FormatException)
+ {
+ Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId);
+ return;
+ }
}
- _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest);
+ _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None);
}
/// <summary>
@@ -196,7 +205,7 @@ namespace MediaBrowser.Api.Syncplay
public void Post(SyncplayLeaveGroup request)
{
var currentSession = GetSession(_sessionContext);
- _syncplayManager.LeaveGroup(currentSession);
+ _syncplayManager.LeaveGroup(currentSession, CancellationToken.None);
}
/// <summary>
@@ -207,7 +216,23 @@ namespace MediaBrowser.Api.Syncplay
public List<GroupInfoView> Post(SyncplayListGroups request)
{
var currentSession = GetSession(_sessionContext);
- return _syncplayManager.ListGroups(currentSession);
+ var filterItemId = Guid.Empty;
+ if (!String.IsNullOrEmpty(request.FilterItemId))
+ {
+ try
+ {
+ filterItemId = Guid.Parse(request.FilterItemId);
+ }
+ catch (ArgumentNullException)
+ {
+ Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter.");
+ }
+ catch (FormatException)
+ {
+ Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId);
+ }
+ }
+ return _syncplayManager.ListGroups(currentSession, filterItemId);
}
/// <summary>
@@ -221,7 +246,7 @@ namespace MediaBrowser.Api.Syncplay
{
Type = PlaybackRequestType.Play
};
- _syncplayManager.HandleRequest(currentSession, syncplayRequest);
+ _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None);
}
/// <summary>
@@ -235,7 +260,7 @@ namespace MediaBrowser.Api.Syncplay
{
Type = PlaybackRequestType.Pause
};
- _syncplayManager.HandleRequest(currentSession, syncplayRequest);
+ _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None);
}
/// <summary>
@@ -250,7 +275,7 @@ namespace MediaBrowser.Api.Syncplay
Type = PlaybackRequestType.Seek,
PositionTicks = request.PositionTicks
};
- _syncplayManager.HandleRequest(currentSession, syncplayRequest);
+ _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None);
}
/// <summary>
@@ -262,11 +287,11 @@ namespace MediaBrowser.Api.Syncplay
var currentSession = GetSession(_sessionContext);
var syncplayRequest = new PlaybackRequest()
{
- Type = request.Resume ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering,
+ Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering,
When = DateTime.Parse(request.When),
PositionTicks = request.PositionTicks
};
- _syncplayManager.HandleRequest(currentSession, syncplayRequest);
+ _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None);
}
/// <summary>
@@ -281,7 +306,7 @@ namespace MediaBrowser.Api.Syncplay
Type = PlaybackRequestType.UpdatePing,
Ping = Convert.ToInt64(request.Ping)
};
- _syncplayManager.HandleRequest(currentSession, syncplayRequest);
+ _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None);
}
}
}
diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs
index 930968d9f..9a26ffd99 100644
--- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs
+++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs
@@ -1,7 +1,6 @@
using System;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.Syncplay;
using Microsoft.Extensions.Logging;
@@ -19,16 +18,6 @@ namespace MediaBrowser.Api.Syncplay
/// </summary>
public class TimeSyncService : BaseApiService
{
- /// <summary>
- /// The session manager.
- /// </summary>
- private readonly ISessionManager _sessionManager;
-
- /// <summary>
- /// The session context.
- /// </summary>
- private readonly ISessionContext _sessionContext;
-
public TimeSyncService(
ILogger<TimeSyncService> logger,
IServerConfigurationManager serverConfigurationManager,
@@ -55,7 +44,7 @@ namespace MediaBrowser.Api.Syncplay
var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o");
response.ResponseTransmissionTime = responseTransmissionTime;
- // Implementing NTP on such a high level results in this useless
+ // Implementing NTP on such a high level results in this useless
// information being sent. On the other hand it enables future additions.
return response;
}
diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs
index 8e886a2cb..c01fead83 100644
--- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs
+++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Session;
@@ -9,6 +8,9 @@ namespace MediaBrowser.Controller.Syncplay
/// <summary>
/// Class GroupInfo.
/// </summary>
+ /// <remarks>
+ /// Class is not thread-safe, external locking is required when accessing methods.
+ /// </remarks>
public class GroupInfo
{
/// <summary>
@@ -49,8 +51,8 @@ namespace MediaBrowser.Controller.Syncplay
/// Gets the participants.
/// </summary>
/// <value>The participants, or members of the group.</value>
- public readonly ConcurrentDictionary<string, GroupMember> Participants =
- new ConcurrentDictionary<string, GroupMember>(StringComparer.OrdinalIgnoreCase);
+ public readonly Dictionary<string, GroupMember> Participants =
+ new Dictionary<string, GroupMember>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Checks if a session is in this group.
diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs
index 5b08eac0a..34eae4062 100644
--- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs
+++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Syncplay;
@@ -31,27 +32,31 @@ namespace MediaBrowser.Controller.Syncplay
/// Initializes the group with the session's info.
/// </summary>
/// <param name="session">The session.</param>
- void InitGroup(SessionInfo session);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ void InitGroup(SessionInfo session, CancellationToken cancellationToken);
/// <summary>
/// Adds the session to the group.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="request">The request.</param>
- void SessionJoin(SessionInfo session, JoinGroupRequest request);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken);
/// <summary>
/// Removes the session from the group.
/// </summary>
/// <param name="session">The session.</param>
- void SessionLeave(SessionInfo session);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ void SessionLeave(SessionInfo session, CancellationToken cancellationToken);
/// <summary>
/// Handles the requested action by the session.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="request">The requested action.</param>
- void HandleRequest(SessionInfo session, PlaybackRequest request);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken);
/// <summary>
/// Gets the info about the group for the clients.
diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs
index 433d6d8bc..fbc208d27 100644
--- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs
+++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Syncplay;
@@ -14,7 +15,8 @@ namespace MediaBrowser.Controller.Syncplay
/// Creates a new group.
/// </summary>
/// <param name="session">The session that's creating the group.</param>
- void NewGroup(SessionInfo session);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ void NewGroup(SessionInfo session, CancellationToken cancellationToken);
/// <summary>
/// Adds the session to a group.
@@ -22,27 +24,31 @@ namespace MediaBrowser.Controller.Syncplay
/// <param name="session">The session.</param>
/// <param name="groupId">The group id.</param>
/// <param name="request">The request.</param>
- void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken);
/// <summary>
/// Removes the session from a group.
/// </summary>
/// <param name="session">The session.</param>
- void LeaveGroup(SessionInfo session);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ void LeaveGroup(SessionInfo session, CancellationToken cancellationToken);
/// <summary>
/// Gets list of available groups for a session.
/// </summary>
/// <param name="session">The session.</param>
+ /// <param name="filterItemId">The item id to filter by.</param>
/// <value>The list of available groups.</value>
- List<GroupInfoView> ListGroups(SessionInfo session);
+ List<GroupInfoView> ListGroups(SessionInfo session, Guid filterItemId);
/// <summary>
/// Handle a request by a session in a group.
/// </summary>
/// <param name="session">The session.</param>
/// <param name="request">The request.</param>
- void HandleRequest(SessionInfo session, PlaybackRequest request);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken);
/// <summary>
/// Maps a session to a group.
diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs
index 20e76932d..9f40f9577 100644
--- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs
+++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs
@@ -30,13 +30,13 @@ namespace MediaBrowser.Model.Syncplay
/// </summary>
PrepareSession,
/// <summary>
- /// The not-in-group error. Tells a user that it doesn't belong to a group.
+ /// The not-in-group error. Tells a user that they don't belong to a group.
/// </summary>
NotInGroup,
/// <summary>
- /// The group-not-joined error. Sent when a request to join a group fails.
+ /// The group-does-not-exist error. Sent when trying to join a non-existing group.
/// </summary>
- GroupNotJoined,
+ GroupDoesNotExist,
/// <summary>
/// The create-group-denied error. Sent when a user tries to create a group without required permissions.
/// </summary>
diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs
index cae769db0..ba97641f6 100644
--- a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs
+++ b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Syncplay
/// Gets or sets the request type.
/// </summary>
/// <value>The request type.</value>
- public PlaybackRequestType Type;
+ public PlaybackRequestType Type { get; set; }
/// <summary>
/// Gets or sets when the request has been made by the client.