keep playstate during syncplay group creation

This commit is contained in:
dkanada 2020-07-13 06:55:03 +09:00
parent 04648c7e98
commit 5e706ba7ce
8 changed files with 41 additions and 64 deletions

View File

@ -194,26 +194,24 @@ namespace Emby.Server.Implementations.SyncPlay
}
/// <inheritdoc />
public void InitGroup(SessionInfo session, CancellationToken cancellationToken)
public void CreateGroup(SessionInfo session, CancellationToken cancellationToken)
{
_group.AddSession(session);
_syncPlayManager.AddSessionToGroup(session, this);
_group.PlayingItem = session.FullNowPlayingItem;
_group.IsPaused = true;
_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);
var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause);
SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
}
/// <inheritdoc />
public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken)
{
if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id)
if (session.NowPlayingItem?.Id == _group.PlayingItem.Id)
{
_group.AddSession(session);
_syncPlayManager.AddSessionToGroup(session, this);
@ -224,7 +222,7 @@ namespace Emby.Server.Implementations.SyncPlay
var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
// Client join and play, syncing will happen client side
// syncing will happen client side
if (!_group.IsPaused)
{
var playCommand = NewSyncPlayCommand(SendCommandType.Play);
@ -262,10 +260,9 @@ namespace Emby.Server.Implementations.SyncPlay
/// <inheritdoc />
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.
// The actual syncing of media playback happens client side.
// Clients are aware of the server's time and use it to sync.
// 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:
@ -277,13 +274,13 @@ namespace Emby.Server.Implementations.SyncPlay
case PlaybackRequestType.Seek:
HandleSeekRequest(session, request, cancellationToken);
break;
case PlaybackRequestType.Buffering:
case PlaybackRequestType.Buffer:
HandleBufferingRequest(session, request, cancellationToken);
break;
case PlaybackRequestType.BufferingDone:
case PlaybackRequestType.Ready:
HandleBufferingDoneRequest(session, request, cancellationToken);
break;
case PlaybackRequestType.UpdatePing:
case PlaybackRequestType.Ping:
HandlePingUpdateRequest(session, request);
break;
}
@ -301,7 +298,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Pick a suitable time that accounts for latency
var delay = _group.GetHighestPing() * 2;
delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
// Unpause group and set starting point in future
// Clients will start playback at LastActivity (datetime) from PositionTicks (playback position)
@ -337,8 +334,9 @@ namespace Emby.Server.Implementations.SyncPlay
var currentTime = DateTime.UtcNow;
var elapsedTime = currentTime - _group.LastActivity;
_group.LastActivity = currentTime;
// Seek only if playback actually started
// (a pause request may be issued during the delay added to account for latency)
// 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);
@ -451,7 +449,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Client, that was buffering, resumed playback but did not update others in time
delay = _group.GetHighestPing() * 2;
delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
_group.LastActivity = currentTime.AddMilliseconds(
delay);
@ -495,7 +493,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.DefaultPing);
}
/// <inheritdoc />

View File

@ -170,10 +170,11 @@ namespace Emby.Server.Implementations.SyncPlay
{
_logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id);
var error = new GroupUpdate<string>()
var error = new GroupUpdate<string>
{
Type = GroupUpdateType.CreateGroupDenied
};
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@ -188,7 +189,7 @@ namespace Emby.Server.Implementations.SyncPlay
var group = new SyncPlayController(_sessionManager, this);
_groups[group.GetGroupId()] = group;
group.InitGroup(session, cancellationToken);
group.CreateGroup(session, cancellationToken);
}
}
@ -205,6 +206,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
Type = GroupUpdateType.JoinGroupDenied
};
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@ -300,9 +302,9 @@ namespace Emby.Server.Implementations.SyncPlay
group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())).Select(
group => group.GetInfo()).ToList();
}
// Otherwise show all available groups
else
{
// Otherwise show all available groups
return _groups.Values.Where(
group => HasAccessToItem(user, group.GetPlayingItemId())).Select(
group => group.GetInfo()).ToList();
@ -322,6 +324,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
Type = GroupUpdateType.JoinGroupDenied
};
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@ -366,7 +369,6 @@ namespace Emby.Server.Implementations.SyncPlay
}
_sessionToGroupMap.Remove(session.Id, out var tempGroup);
if (!tempGroup.GetGroupId().Equals(group.GetGroupId()))
{
throw new InvalidOperationException("Session was in wrong group!");

View File

@ -27,13 +27,6 @@ namespace MediaBrowser.Api.SyncPlay
/// <value>The Group id to join.</value>
[ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string GroupId { get; set; }
/// <summary>
/// Gets or sets the playing item id.
/// </summary>
/// <value>The client's currently playing item id.</value>
[ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string PlayingItemId { get; set; }
}
[Route("/SyncPlay/Leave", "POST", Summary = "Leave joined SyncPlay group")]
@ -89,7 +82,7 @@ namespace MediaBrowser.Api.SyncPlay
public long PositionTicks { get; set; }
/// <summary>
/// Gets or sets whether this is a buffering or a buffering-done request.
/// Gets or sets whether this is a buffering or a ready request.
/// </summary>
/// <value><c>true</c> if buffering is complete; <c>false</c> otherwise.</value>
[ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
@ -150,25 +143,15 @@ namespace MediaBrowser.Api.SyncPlay
var currentSession = GetSession(_sessionContext);
Guid groupId;
Guid playingItemId = Guid.Empty;
if (!Guid.TryParse(request.GroupId, out groupId))
{
Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId);
return;
}
// Both null and empty strings mean that client isn't playing anything
if (!string.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId))
{
Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId);
return;
}
var joinRequest = new JoinGroupRequest()
{
GroupId = groupId,
PlayingItemId = playingItemId
GroupId = groupId
};
_syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None);
@ -254,7 +237,7 @@ namespace MediaBrowser.Api.SyncPlay
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
{
Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering,
Type = request.BufferingDone ? PlaybackRequestType.Ready : PlaybackRequestType.Buffer,
When = DateTime.Parse(request.When),
PositionTicks = request.PositionTicks
};
@ -270,7 +253,7 @@ namespace MediaBrowser.Api.SyncPlay
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
{
Type = PlaybackRequestType.UpdatePing,
Type = PlaybackRequestType.Ping,
Ping = Convert.ToInt64(request.Ping)
};
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);

View File

@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <summary>
/// Gets the default ping value used for sessions.
/// </summary>
public long DefaulPing { get; } = 500;
public long DefaultPing { get; } = 500;
/// <summary>
/// Gets or sets the group identifier.
@ -70,16 +70,16 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public void AddSession(SessionInfo session)
{
if (ContainsSession(session.Id.ToString()))
if (ContainsSession(session.Id))
{
return;
}
var member = new GroupMember();
member.Session = session;
member.Ping = DefaulPing;
member.Ping = DefaultPing;
member.IsBuffering = false;
Participants[session.Id.ToString()] = member;
Participants[session.Id] = member;
}
/// <summary>
@ -88,12 +88,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public void RemoveSession(SessionInfo session)
{
if (!ContainsSession(session.Id.ToString()))
if (!ContainsSession(session.Id))
{
return;
}
Participants.Remove(session.Id.ToString(), out _);
Participants.Remove(session.Id, out _);
}
/// <summary>
@ -103,12 +103,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="ping">The ping.</param>
public void UpdatePing(SessionInfo session, long ping)
{
if (!ContainsSession(session.Id.ToString()))
if (!ContainsSession(session.Id))
{
return;
}
Participants[session.Id.ToString()].Ping = ping;
Participants[session.Id].Ping = ping;
}
/// <summary>
@ -117,7 +117,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <value name="session">The highest ping in the group.</value>
public long GetHighestPing()
{
long max = Int64.MinValue;
long max = long.MinValue;
foreach (var session in Participants.Values)
{
max = Math.Max(max, session.Ping);
@ -133,12 +133,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="isBuffering">The state.</param>
public void SetBuffering(SessionInfo session, bool isBuffering)
{
if (!ContainsSession(session.Id.ToString()))
if (!ContainsSession(session.Id))
{
return;
}
Participants[session.Id.ToString()].IsBuffering = isBuffering;
Participants[session.Id].IsBuffering = isBuffering;
}
/// <summary>

View File

@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.SyncPlay
public class GroupMember
{
/// <summary>
/// Gets or sets whether this member is buffering.
/// Gets or sets a value indicating whether this member is buffering.
/// </summary>
/// <value><c>true</c> if member is buffering; <c>false</c> otherwise.</value>
public bool IsBuffering { get; set; }

View File

@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The cancellation token.</param>
void InitGroup(SessionInfo session, CancellationToken cancellationToken);
void CreateGroup(SessionInfo session, CancellationToken cancellationToken);
/// <summary>
/// Adds the session to the group.

View File

@ -12,11 +12,5 @@ namespace MediaBrowser.Model.SyncPlay
/// </summary>
/// <value>The Group id to join.</value>
public Guid GroupId { get; set; }
/// <summary>
/// Gets or sets the playing item id.
/// </summary>
/// <value>The client's currently playing item id.</value>
public Guid PlayingItemId { get; set; }
}
}

View File

@ -23,16 +23,16 @@ namespace MediaBrowser.Model.SyncPlay
/// <summary>
/// A user is signaling that playback is buffering.
/// </summary>
Buffering = 3,
Buffer = 3,
/// <summary>
/// A user is signaling that playback resumed.
/// </summary>
BufferingDone = 4,
Ready = 4,
/// <summary>
/// A user is reporting its ping.
/// </summary>
UpdatePing = 5
Ping = 5
}
}