Przeglądaj źródła

Fix SyncPlay WebSocket OpenAPI schemas (#13946)

Niels van Velzen 1 miesiąc temu
rodzic
commit
269508be9f
24 zmienionych plików z 299 dodań i 242 usunięć
  1. 5 11
      Emby.Server.Implementations/SyncPlay/Group.cs
  2. 4 4
      Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
  3. 72 33
      Jellyfin.Server/Filters/AdditionalModelFilter.cs
  4. 0 24
      MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs
  5. 0 25
      MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs
  6. 0 25
      MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs
  7. 0 25
      MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs
  8. 0 25
      MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs
  9. 1 1
      MediaBrowser.Controller/Session/ISessionManager.cs
  10. 6 6
      MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs
  11. 8 8
      MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
  12. 1 10
      MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs
  13. 13 4
      MediaBrowser.Model/SyncPlay/GroupUpdate.cs
  14. 0 31
      MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs
  15. 0 10
      MediaBrowser.Model/SyncPlay/GroupUpdateType.cs
  16. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayGroupDoesNotExistUpdate.cs
  17. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayGroupJoinedUpdate.cs
  18. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayGroupLeftUpdate.cs
  19. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayLibraryAccessDeniedUpdate.cs
  20. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayNotInGroupUpdate.cs
  21. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayPlayQueueUpdate.cs
  22. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayStateUpdate.cs
  23. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayUserJoinedUpdate.cs
  24. 21 0
      MediaBrowser.Model/SyncPlay/SyncPlayUserLeftUpdate.cs

+ 5 - 11
Emby.Server.Implementations/SyncPlay/Group.cs

@@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.SyncPlay
                 SetState(waitingState);
             }
 
-            var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo());
+            var updateSession = new SyncPlayGroupJoinedUpdate(GroupId, GetInfo());
             SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 
             _state.SessionJoined(this, _state.Type, session, cancellationToken);
@@ -291,10 +291,10 @@ namespace Emby.Server.Implementations.SyncPlay
         {
             AddSession(session);
 
-            var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo());
+            var updateSession = new SyncPlayGroupJoinedUpdate(GroupId, GetInfo());
             SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 
-            var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
+            var updateOthers = new SyncPlayUserJoinedUpdate(GroupId, session.UserName);
             SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 
             _state.SessionJoined(this, _state.Type, session, cancellationToken);
@@ -314,10 +314,10 @@ namespace Emby.Server.Implementations.SyncPlay
 
             RemoveSession(session);
 
-            var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, GroupId.ToString());
+            var updateSession = new SyncPlayGroupLeftUpdate(GroupId, GroupId.ToString());
             SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 
-            var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName);
+            var updateOthers = new SyncPlayUserLeftUpdate(GroupId, session.UserName);
             SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 
             _logger.LogInformation("Session {SessionId} left group {GroupId}.", session.Id, GroupId.ToString());
@@ -425,12 +425,6 @@ namespace Emby.Server.Implementations.SyncPlay
                 DateTime.UtcNow);
         }
 
-        /// <inheritdoc />
-        public GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data)
-        {
-            return new GroupUpdate<T>(GroupId, type, data);
-        }
-
         /// <inheritdoc />
         public long SanitizePositionTicks(long? positionTicks)
         {

+ 4 - 4
Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs

@@ -159,7 +159,7 @@ namespace Emby.Server.Implementations.SyncPlay
                 {
                     _logger.LogWarning("Session {SessionId} tried to join group {GroupId} that does not exist.", session.Id, request.GroupId);
 
-                    var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.GroupDoesNotExist, string.Empty);
+                    var error = new SyncPlayGroupDoesNotExistUpdate(Guid.Empty, string.Empty);
                     _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
                     return;
                 }
@@ -171,7 +171,7 @@ namespace Emby.Server.Implementations.SyncPlay
                     {
                         _logger.LogWarning("Session {SessionId} tried to join group {GroupId} but does not have access to some content of the playing queue.", session.Id, group.GroupId.ToString());
 
-                        var error = new GroupUpdate<string>(group.GroupId, GroupUpdateType.LibraryAccessDenied, string.Empty);
+                        var error = new SyncPlayLibraryAccessDeniedUpdate(group.GroupId, string.Empty);
                         _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
                         return;
                     }
@@ -248,7 +248,7 @@ namespace Emby.Server.Implementations.SyncPlay
                 {
                     _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id);
 
-                    var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty);
+                    var error = new SyncPlayNotInGroupUpdate(Guid.Empty, string.Empty);
                     _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
                 }
             }
@@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.SyncPlay
             {
                 _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id);
 
-                var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty);
+                var error = new SyncPlayNotInGroupUpdate(Guid.Empty, string.Empty);
                 _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
             }
         }

+ 72 - 33
Jellyfin.Server/Filters/AdditionalModelFilter.cs

@@ -92,17 +92,51 @@ namespace Jellyfin.Server.Filters
                     continue;
                 }
 
-                // Additional discriminator needed for GroupUpdate models...
-                if (messageType == SessionMessageType.SyncPlayGroupUpdate && type != typeof(SyncPlayGroupUpdateCommandMessage))
-                {
-                    continue;
-                }
-
                 var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
                 outboundWebSocketSchemas.Add(schema);
                 outboundWebSocketDiscriminators.Add(messageType.ToString()!, schema.Reference.ReferenceV3);
             }
 
+            // Add custom "SyncPlayGroupUpdateMessage" schema because Swashbuckle cannot generate it for us
+            var syncPlayGroupUpdateMessageSchema = new OpenApiSchema
+            {
+                Type = "object",
+                Description = "Untyped sync play command.",
+                Properties = new Dictionary<string, OpenApiSchema>
+                {
+                    {
+                        "Data", new OpenApiSchema
+                        {
+                            AllOf =
+                            [
+                                new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(GroupUpdate<object>) } }
+                            ],
+                            Description = "Group update data",
+                            Nullable = false,
+                        }
+                    },
+                    { "MessageId", new OpenApiSchema { Type = "string", Format = "uuid", Description = "Gets or sets the message id." } },
+                    {
+                        "MessageType", new OpenApiSchema
+                        {
+                            Enum = Enum.GetValues<SessionMessageType>().Select(type => new OpenApiString(type.ToString())).ToList<IOpenApiAny>(),
+                            AllOf =
+                            [
+                                new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(SessionMessageType) } }
+                            ],
+                            Description = "The different kinds of messages that are used in the WebSocket api.",
+                            Default = new OpenApiString(nameof(SessionMessageType.SyncPlayGroupUpdate)),
+                            ReadOnly = true
+                        }
+                    },
+                },
+                AdditionalPropertiesAllowed = false,
+                Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "SyncPlayGroupUpdateMessage" }
+            };
+            context.SchemaRepository.AddDefinition("SyncPlayGroupUpdateMessage", syncPlayGroupUpdateMessageSchema);
+            outboundWebSocketSchemas.Add(syncPlayGroupUpdateMessageSchema);
+            outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayGroupUpdateMessageSchema.Reference.ReferenceV3;
+
             var outboundWebSocketMessageSchema = new OpenApiSchema
             {
                 Type = "object",
@@ -140,41 +174,46 @@ namespace Jellyfin.Server.Filters
                 });
 
             // Manually generate sync play GroupUpdate messages.
-            if (!context.SchemaRepository.Schemas.TryGetValue(nameof(GroupUpdate), out var groupUpdateSchema))
-            {
-                groupUpdateSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository);
-            }
-
-            var groupUpdateOfGroupInfoSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<GroupInfoDto>), context.SchemaRepository);
-            var groupUpdateOfGroupStateSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<GroupStateUpdate>), context.SchemaRepository);
-            var groupUpdateOfStringSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<string>), context.SchemaRepository);
-            var groupUpdateOfPlayQueueSchema = context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<PlayQueueUpdate>), context.SchemaRepository);
+            var groupUpdateTypes = typeof(GroupUpdate<>).Assembly.GetTypes()
+                .Where(t => t.BaseType != null
+                            && t.BaseType.IsGenericType
+                            && t.BaseType.GetGenericTypeDefinition() == typeof(GroupUpdate<>))
+                .ToList();
 
-            groupUpdateSchema.OneOf = new List<OpenApiSchema>
+            var groupUpdateSchemas = new List<OpenApiSchema>();
+            var groupUpdateDiscriminators = new Dictionary<string, string>();
+            foreach (var type in groupUpdateTypes)
             {
-                groupUpdateOfGroupInfoSchema,
-                groupUpdateOfGroupStateSchema,
-                groupUpdateOfStringSchema,
-                groupUpdateOfPlayQueueSchema
-            };
+                var groupUpdateType = (GroupUpdateType?)type.GetProperty(nameof(GroupUpdate<object>.Type))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
+                if (groupUpdateType is null)
+                {
+                    continue;
+                }
+
+                var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
+                groupUpdateSchemas.Add(schema);
+                groupUpdateDiscriminators[groupUpdateType.ToString()!] = schema.Reference.ReferenceV3;
+            }
 
-            groupUpdateSchema.Discriminator = new OpenApiDiscriminator
+            var groupUpdateSchema = new OpenApiSchema
             {
-                PropertyName = nameof(GroupUpdate.Type),
-                Mapping = new Dictionary<string, string>
+                Type = "object",
+                Description = "Represents the list of possible group update types",
+                Reference = new OpenApiReference
+                {
+                    Id = nameof(GroupUpdate<object>),
+                    Type = ReferenceType.Schema
+                },
+                OneOf = groupUpdateSchemas,
+                Discriminator = new OpenApiDiscriminator
                 {
-                    { GroupUpdateType.UserJoined.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 },
-                    { GroupUpdateType.UserLeft.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 },
-                    { GroupUpdateType.GroupJoined.ToString(), groupUpdateOfGroupInfoSchema.Reference.ReferenceV3 },
-                    { GroupUpdateType.GroupLeft.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 },
-                    { GroupUpdateType.StateUpdate.ToString(), groupUpdateOfGroupStateSchema.Reference.ReferenceV3 },
-                    { GroupUpdateType.PlayQueue.ToString(), groupUpdateOfPlayQueueSchema.Reference.ReferenceV3 },
-                    { GroupUpdateType.NotInGroup.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 },
-                    { GroupUpdateType.GroupDoesNotExist.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 },
-                    { GroupUpdateType.LibraryAccessDenied.ToString(), groupUpdateOfStringSchema.Reference.ReferenceV3 }
+                    PropertyName = nameof(GroupUpdate<object>.Type),
+                    Mapping = groupUpdateDiscriminators
                 }
             };
 
+            context.SchemaRepository.Schemas[nameof(GroupUpdate<object>)] = groupUpdateSchema;
+
             context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository);
 
             foreach (var configuration in _serverConfigurationManager.GetConfigurationStores())

+ 0 - 24
MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs

@@ -1,24 +0,0 @@
-using System.ComponentModel;
-using MediaBrowser.Model.Session;
-using MediaBrowser.Model.SyncPlay;
-
-namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
-
-/// <summary>
-/// Untyped sync play command.
-/// </summary>
-public class SyncPlayGroupUpdateCommandMessage : OutboundWebSocketMessage<GroupUpdate>
-{
-    /// <summary>
-    /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandMessage"/> class.
-    /// </summary>
-    /// <param name="data">The send command.</param>
-    public SyncPlayGroupUpdateCommandMessage(GroupUpdate data)
-        : base(data)
-    {
-    }
-
-    /// <inheritdoc />
-    [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
-    public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
-}

+ 0 - 25
MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs

@@ -1,25 +0,0 @@
-using System.ComponentModel;
-using MediaBrowser.Model.Session;
-using MediaBrowser.Model.SyncPlay;
-
-namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
-
-/// <summary>
-/// Sync play group update command with group info.
-/// GroupUpdateTypes: GroupJoined.
-/// </summary>
-public class SyncPlayGroupUpdateCommandOfGroupInfoMessage : OutboundWebSocketMessage<GroupUpdate<GroupInfoDto>>
-{
-    /// <summary>
-    /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfGroupInfoMessage"/> class.
-    /// </summary>
-    /// <param name="data">The group info.</param>
-    public SyncPlayGroupUpdateCommandOfGroupInfoMessage(GroupUpdate<GroupInfoDto> data)
-        : base(data)
-    {
-    }
-
-    /// <inheritdoc />
-    [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
-    public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
-}

+ 0 - 25
MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs

@@ -1,25 +0,0 @@
-using System.ComponentModel;
-using MediaBrowser.Model.Session;
-using MediaBrowser.Model.SyncPlay;
-
-namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
-
-/// <summary>
-/// Sync play group update command with group state update.
-/// GroupUpdateTypes: StateUpdate.
-/// </summary>
-public class SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage : OutboundWebSocketMessage<GroupUpdate<GroupStateUpdate>>
-{
-    /// <summary>
-    /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage"/> class.
-    /// </summary>
-    /// <param name="data">The group info.</param>
-    public SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage(GroupUpdate<GroupStateUpdate> data)
-        : base(data)
-    {
-    }
-
-    /// <inheritdoc />
-    [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
-    public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
-}

+ 0 - 25
MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs

@@ -1,25 +0,0 @@
-using System.ComponentModel;
-using MediaBrowser.Model.Session;
-using MediaBrowser.Model.SyncPlay;
-
-namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
-
-/// <summary>
-/// Sync play group update command with play queue update.
-/// GroupUpdateTypes: PlayQueue.
-/// </summary>
-public class SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage : OutboundWebSocketMessage<GroupUpdate<PlayQueueUpdate>>
-{
-    /// <summary>
-    /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage"/> class.
-    /// </summary>
-    /// <param name="data">The play queue update.</param>
-    public SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage(GroupUpdate<PlayQueueUpdate> data)
-        : base(data)
-    {
-    }
-
-    /// <inheritdoc />
-    [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
-    public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
-}

+ 0 - 25
MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs

@@ -1,25 +0,0 @@
-using System.ComponentModel;
-using MediaBrowser.Model.Session;
-using MediaBrowser.Model.SyncPlay;
-
-namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
-
-/// <summary>
-/// Sync play group update command with string.
-/// GroupUpdateTypes: GroupDoesNotExist (error), LibraryAccessDenied (error), NotInGroup (error), GroupLeft (groupId), UserJoined (username), UserLeft (username).
-/// </summary>
-public class SyncPlayGroupUpdateCommandOfStringMessage : OutboundWebSocketMessage<GroupUpdate<string>>
-{
-    /// <summary>
-    /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfStringMessage"/> class.
-    /// </summary>
-    /// <param name="data">The send command.</param>
-    public SyncPlayGroupUpdateCommandOfStringMessage(GroupUpdate<string> data)
-        : base(data)
-    {
-    }
-
-    /// <inheritdoc />
-    [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
-    public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
-}

+ 1 - 1
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -161,7 +161,7 @@ namespace MediaBrowser.Controller.Session
         /// <param name="sessionId">The identifier of the session.</param>
         /// <param name="command">The group update.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <typeparam name="T">Type of group.</typeparam>
+        /// <typeparam name="T">The group update type.</typeparam>
         /// <returns>Task.</returns>
         Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken);
 

+ 6 - 6
MediaBrowser.Controller/SyncPlay/GroupStates/AbstractGroupState.cs

@@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
             }
 
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RemoveItems);
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 
             if (playingItemRemoved && !context.PlayQueue.IsItemPlaying())
@@ -106,7 +106,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
             }
 
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.MoveItem);
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
         }
 
@@ -127,7 +127,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
                 _ => PlayQueueUpdateReason.Queue
             };
             var playQueueUpdate = context.GetPlayQueueUpdate(reason);
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
         }
 
@@ -184,7 +184,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
         {
             context.SetRepeatMode(request.Mode);
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RepeatMode);
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
         }
 
@@ -193,7 +193,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
         {
             context.SetShuffleMode(request.Mode);
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.ShuffleMode);
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
         }
 
@@ -221,7 +221,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
         {
             // Notify relevant state change event.
             var stateUpdate = new GroupStateUpdate(Type, reason.Action);
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.StateUpdate, stateUpdate);
+            var update = new SyncPlayStateUpdate(context.GroupId, stateUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
         }
 

+ 8 - 8
MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs

@@ -78,7 +78,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
 
             // Prepare new session.
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
 
             context.SetBuffering(session, true);
@@ -152,7 +152,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
             }
 
             var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
-            var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
             context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 
             // Reset status of sessions and await for all Ready events.
@@ -177,7 +177,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
             if (result)
             {
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
-                var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
                 context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 
                 // Reset status of sessions and await for all Ready events.
@@ -215,7 +215,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
                 context.RestartCurrentItem();
 
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
-                var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
                 context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 
                 // Reset status of sessions and await for all Ready events.
@@ -336,7 +336,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
                 _logger.LogDebug("Session {SessionId} reported wrong playlist item in group {GroupId}.", session.Id, context.GroupId.ToString());
 
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
-                var updateSession = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+                var updateSession = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
                 context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
                 context.SetBuffering(session, true);
 
@@ -410,7 +410,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
                 _logger.LogDebug("Session {SessionId} reported wrong playlist item in group {GroupId}.", session.Id, context.GroupId.ToString());
 
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
-                var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
                 context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
                 context.SetBuffering(session, true);
 
@@ -583,7 +583,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
             {
                 // Send playing-queue update.
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NextItem);
-                var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
                 context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 
                 // Reset status of sessions and await for all Ready events.
@@ -629,7 +629,7 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
             {
                 // Send playing-queue update.
                 var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.PreviousItem);
-                var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
+                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
                 context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 
                 // Reset status of sessions and await for all Ready events.

+ 1 - 10
MediaBrowser.Controller/SyncPlay/IGroupStateContext.cs

@@ -66,11 +66,11 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <summary>
         /// Sends a GroupUpdate message to the interested sessions.
         /// </summary>
-        /// <typeparam name="T">The type of the data of the message.</typeparam>
         /// <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>
+        /// <typeparam name="T">The group update type.</typeparam>
         /// <returns>The task.</returns>
         Task SendGroupUpdate<T>(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken);
 
@@ -91,15 +91,6 @@ namespace MediaBrowser.Controller.SyncPlay
         /// <returns>The command.</returns>
         SendCommand NewSyncPlayCommand(SendCommandType type);
 
-        /// <summary>
-        /// Builds a new group update message.
-        /// </summary>
-        /// <typeparam name="T">The type of the data of the message.</typeparam>
-        /// <param name="type">The update type.</param>
-        /// <param name="data">The data to send.</param>
-        /// <returns>The group update.</returns>
-        GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data);
-
         /// <summary>
         /// Sanitizes the PositionTicks, considers the current playing item when available.
         /// </summary>

+ 13 - 4
MediaBrowser.Model/SyncPlay/GroupUpdate.cs

@@ -5,15 +5,18 @@ namespace MediaBrowser.Model.SyncPlay;
 /// <summary>
 /// Group update without data.
 /// </summary>
-public abstract class GroupUpdate
+/// <typeparam name="T">The type of the update data.</typeparam>
+public abstract class GroupUpdate<T>
 {
     /// <summary>
-    /// Initializes a new instance of the <see cref="GroupUpdate"/> class.
+    /// Initializes a new instance of the <see cref="GroupUpdate{T}"/> class.
     /// </summary>
     /// <param name="groupId">The group identifier.</param>
-    protected GroupUpdate(Guid groupId)
+    /// <param name="data">The update data.</param>
+    protected GroupUpdate(Guid groupId, T data)
     {
         GroupId = groupId;
+        Data = data;
     }
 
     /// <summary>
@@ -22,9 +25,15 @@ public abstract class GroupUpdate
     /// <value>The group identifier.</value>
     public Guid GroupId { get; }
 
+    /// <summary>
+    /// Gets the update data.
+    /// </summary>
+    /// <value>The update data.</value>
+    public T Data { get; }
+
     /// <summary>
     /// Gets the update type.
     /// </summary>
     /// <value>The update type.</value>
-    public GroupUpdateType Type { get; init; }
+    public abstract GroupUpdateType Type { get; }
 }

+ 0 - 31
MediaBrowser.Model/SyncPlay/GroupUpdateOfT.cs

@@ -1,31 +0,0 @@
-#pragma warning disable SA1649
-
-using System;
-
-namespace MediaBrowser.Model.SyncPlay;
-
-/// <summary>
-/// Class GroupUpdate.
-/// </summary>
-/// <typeparam name="T">The type of the data of the message.</typeparam>
-public class GroupUpdate<T> : GroupUpdate
-{
-    /// <summary>
-    /// Initializes a new instance of the <see cref="GroupUpdate{T}"/> class.
-    /// </summary>
-    /// <param name="groupId">The group identifier.</param>
-    /// <param name="type">The update type.</param>
-    /// <param name="data">The update data.</param>
-    public GroupUpdate(Guid groupId, GroupUpdateType type, T data)
-        : base(groupId)
-    {
-        Data = data;
-        Type = type;
-    }
-
-    /// <summary>
-    /// Gets the update data.
-    /// </summary>
-    /// <value>The update data.</value>
-    public T Data { get; }
-}

+ 0 - 10
MediaBrowser.Model/SyncPlay/GroupUpdateType.cs

@@ -45,16 +45,6 @@ namespace MediaBrowser.Model.SyncPlay
         /// </summary>
         GroupDoesNotExist,
 
-        /// <summary>
-        /// The create-group-denied error. Sent when a user tries to create a group without required permissions.
-        /// </summary>
-        CreateGroupDenied,
-
-        /// <summary>
-        /// The join-group-denied error. Sent when a user tries to join a group without required permissions.
-        /// </summary>
-        JoinGroupDenied,
-
         /// <summary>
         /// The library-access-denied error. Sent when a user tries to join a group without required access to the library.
         /// </summary>

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayGroupDoesNotExistUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayGroupDoesNotExistUpdate : GroupUpdate<string>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayGroupDoesNotExistUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayGroupDoesNotExistUpdate(Guid groupId, string data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.GroupDoesNotExist)]
+    public override GroupUpdateType Type => GroupUpdateType.GroupDoesNotExist;
+}

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayGroupJoinedUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayGroupJoinedUpdate : GroupUpdate<GroupInfoDto>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayGroupJoinedUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayGroupJoinedUpdate(Guid groupId, GroupInfoDto data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.GroupJoined)]
+    public override GroupUpdateType Type => GroupUpdateType.GroupJoined;
+}

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayGroupLeftUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayGroupLeftUpdate : GroupUpdate<string>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayGroupLeftUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayGroupLeftUpdate(Guid groupId, string data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.GroupLeft)]
+    public override GroupUpdateType Type => GroupUpdateType.GroupLeft;
+}

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayLibraryAccessDeniedUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayLibraryAccessDeniedUpdate : GroupUpdate<string>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayLibraryAccessDeniedUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayLibraryAccessDeniedUpdate(Guid groupId, string data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.LibraryAccessDenied)]
+    public override GroupUpdateType Type => GroupUpdateType.LibraryAccessDenied;
+}

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayNotInGroupUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayNotInGroupUpdate : GroupUpdate<string>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayNotInGroupUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayNotInGroupUpdate(Guid groupId, string data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.NotInGroup)]
+    public override GroupUpdateType Type => GroupUpdateType.NotInGroup;
+}

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayPlayQueueUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayPlayQueueUpdate : GroupUpdate<PlayQueueUpdate>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayPlayQueueUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayPlayQueueUpdate(Guid groupId, PlayQueueUpdate data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.PlayQueue)]
+    public override GroupUpdateType Type => GroupUpdateType.PlayQueue;
+}

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayStateUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayStateUpdate : GroupUpdate<GroupStateUpdate>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayStateUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayStateUpdate(Guid groupId, GroupStateUpdate data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.StateUpdate)]
+    public override GroupUpdateType Type => GroupUpdateType.StateUpdate;
+}

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayUserJoinedUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayUserJoinedUpdate : GroupUpdate<string>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayUserJoinedUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayUserJoinedUpdate(Guid groupId, string data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.UserJoined)]
+    public override GroupUpdateType Type => GroupUpdateType.UserJoined;
+}

+ 21 - 0
MediaBrowser.Model/SyncPlay/SyncPlayUserLeftUpdate.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+
+namespace MediaBrowser.Model.SyncPlay;
+
+/// <inheritdoc />
+public class SyncPlayUserLeftUpdate : GroupUpdate<string>
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SyncPlayUserLeftUpdate"/> class.
+    /// </summary>
+    /// <param name="groupId">The groupId.</param>
+    /// <param name="data">The data.</param>
+    public SyncPlayUserLeftUpdate(Guid groupId, string data) : base(groupId, data)
+    {
+    }
+
+    /// <inheritdoc />
+    [DefaultValue(GroupUpdateType.UserLeft)]
+    public override GroupUpdateType Type => GroupUpdateType.UserLeft;
+}