diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs index 3ace87f..cbfa145 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs @@ -489,23 +489,43 @@ ProtocolDefinition protocol } else // Response, Event, Nested { - isConsideredRequired = !typeIsInherentlyNullable && !csharpType.EndsWith("?"); + // The protocol never marks a response or event field optional: all 152 + // response fields and all 149 event fields carry a null valueOptional. The + // only place it records that a field can be absent is the prose, which is + // therefore the signal, rather than whether C# happens to make the type + // nullable. Without this a string OBS always sends still arrives nullable and + // every read needs a "!". + bool proseAllowsNull = DescriptionAllowsNull( + associatedFieldDef.ValueDescription + ); - // Some fields are only ever null in a particular state, which the protocol - // records in the description rather than in valueOptional. Deserializing - // those into a non-nullable value type fails outright when it happens. - if ( - isConsideredRequired - && isValueType - && DescriptionAllowsNull(associatedFieldDef.ValueDescription) - ) + if (proseAllowsNull) { isConsideredRequired = false; } - - if (csharpType.StartsWith("List<") || csharpType.StartsWith("Dictionary<")) + else if (isValueType) { - isConsideredRequired = false; + isConsideredRequired = !csharpType.EndsWith("?"); + } + else + { + // Strings and arrays are always sent. A dictionary or a JsonElement is a + // settings bag that genuinely may not be there, so those stay nullable. + // The array and stub mappers bake the "?" into the type they return, so + // those have to have it stripped here rather than merely left off the + // suffix. A stub is a concrete record for an object OBS always sends, + // unlike a JsonElement settings bag. + bool isList = csharpType.Contains("List<"); + bool isStub = + csharpType.EndsWith("Stub?", StringComparison.Ordinal) + || csharpType.EndsWith("Stub", StringComparison.Ordinal); + isConsideredRequired = csharpType == "string" || isList || isStub; + if ( + (isList || isStub) && csharpType.EndsWith("?", StringComparison.Ordinal) + ) + { + csharpType = csharpType.Substring(0, csharpType.Length - 1); + } } } propertyNullableSuffix = @@ -525,6 +545,19 @@ ProtocolDefinition protocol continue; } + // A field mapped onto a protocol enum needs the wire value on both transports, not the + // member name and not the ordinal. It is never nullable: OBS always sends a value, and + // one it does not recognise maps onto the enum's zero member rather than onto null, so + // a caller never has to null check a state before switching on it. + string? propertyStringEnum = + associatedFieldDef?.ValueType == "String" + ? StringEnumFieldTable.MapStringEnum(originalName) + : null; + if (propertyStringEnum is not null) + { + propertyNullableSuffix = string.Empty; + } + PropertyGenInfo propInfo = new( propertyName, ToCamelCase(propertyName), @@ -561,6 +594,16 @@ ProtocolDefinition protocol mainBuilder.AppendLine($" [JsonPropertyName(\"{originalName}\")]"); mainBuilder.AppendLine($" [Key(\"{originalName}\")]"); + + if (propertyStringEnum is not null) + { + mainBuilder.AppendLine( + $" [JsonConverter(typeof({GeneratedEnumsNamespace}.{propertyStringEnum}JsonConverter))]" + ); + mainBuilder.AppendLine( + $" [MessagePackFormatter(typeof({GeneratedEnumsNamespace}.{propertyStringEnum}MessagePackFormatter))]" + ); + } mainBuilder.Append(" public "); if (isConsideredRequired) { @@ -648,7 +691,9 @@ out bool isRootOfNested // Append optional parameters WITH default null value constructorParams.AddRange( optionalParamsList.Select(p => - $"{p.CSharpType}{p.NullableSuffix} {p.ParamName} = null" + // A non-nullable optional parameter cannot default to null. That happens for + // the enum-typed fields, whose zero member is the "unknown" one anyway. + $"{p.CSharpType}{p.NullableSuffix} {p.ParamName} = {(p.NullableSuffix.Length == 0 ? "default" : "null")}" ) ); diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.EventStreams.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.EventStreams.cs index b48f3a6..c000cf3 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.EventStreams.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.EventStreams.cs @@ -94,15 +94,48 @@ IGrouping group in protocol string eventArgsTypeName = $"{GeneratedEventArgsNamespace}.{eventName}EventArgs"; + string subscriptionRemark = string.IsNullOrWhiteSpace( + eventDef.EventSubscription + ) + ? string.Empty + : $" /// Requires the {System.Security.SecurityElement.Escape(eventDef.EventSubscription)} subscription."; + + string descriptionLine = string.IsNullOrWhiteSpace(eventDef.Description) + ? string.Empty + : $" /// {FlattenDescription(eventDef.Description)}"; + + // The classic handler, on the group rather than only on the client. Explicit + // accessors are what make this work: the group is a struct the property hands + // out fresh, so a field-like event would add to a temporary and lose it. + builder.AppendLine(" /// "); + builder.AppendLine($" /// Occurs when OBS raises {eventName}."); + if (descriptionLine.Length > 0) + { + builder.AppendLine(descriptionLine); + } + + builder.AppendLine(" /// "); + if (subscriptionRemark.Length > 0) + { + builder.AppendLine(subscriptionRemark); + } + + builder.AppendLine( + $" public event EventHandler<{eventArgsTypeName}>? {eventName}" + ); + builder.AppendLine(" {"); + builder.AppendLine($" add => client.{eventName} += value;"); + builder.AppendLine($" remove => client.{eventName} -= value;"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" /// "); builder.AppendLine( $" /// Streams {eventName} events as they arrive." ); - if (!string.IsNullOrWhiteSpace(eventDef.Description)) + if (descriptionLine.Length > 0) { - builder.AppendLine( - $" /// {FlattenDescription(eventDef.Description)}" - ); + builder.AppendLine(descriptionLine); } builder.AppendLine(" /// "); @@ -112,11 +145,9 @@ IGrouping group in protocol builder.AppendLine( " /// Ends the enumeration and unsubscribes." ); - if (!string.IsNullOrWhiteSpace(eventDef.EventSubscription)) + if (subscriptionRemark.Length > 0) { - builder.AppendLine( - $" /// Requires the {System.Security.SecurityElement.Escape(eventDef.EventSubscription)} subscription." - ); + builder.AppendLine(subscriptionRemark); } builder.AppendLine( diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.Helpers.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.Helpers.cs index 60054c5..c373b9a 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.Helpers.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.Helpers.cs @@ -261,9 +261,14 @@ string parentDtoName } } + // A String field that carries a protocol enum is generated as that enum, with converters + // that map to and from the wire value on both transports. + string? stringEnum = + obsType == "String" ? StringEnumFieldTable.MapStringEnum(fieldName) : null; + string? mappedType = obsType switch { - "String" => "string", + "String" => stringEnum is null ? "string" : $"{GeneratedEnumsNamespace}.{stringEnum}", "Number" => numberType, "Boolean" => "bool", "Uuid" => "string", diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs new file mode 100644 index 0000000..52216ac --- /dev/null +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs @@ -0,0 +1,102 @@ +// ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace ObsWebSocket.Codegen.Tasks.Generation; + +/// +/// Emits the wire keys each response record expects, so a payload can be checked against the type +/// it is about to be read as. +/// +internal static partial class Emitter +{ + /// + /// Generates a lookup from response record name to the field names OBS sends for it. + /// + /// + /// Reading a payload as the wrong record is silent on MessagePack, which maps by key name and + /// leaves everything unmatched at its default, and silent on JSON too for the records that + /// happen to have no required member. Response records almost never share field names, so + /// checking that a payload carries at least one key the target record knows catches the + /// mistake on both transports. + /// + /// The source production context. + /// The parsed protocol definition. + public static void GeneratePayloadSchema( + SourceProductionContext context, + ProtocolDefinition protocol + ) + { + if (protocol.Requests is null || protocol.Requests.Count == 0) + { + return; + } + + StringBuilder builder = BuildSourceHeader("// Wire keys per response record"); + builder.AppendLine("using System;"); + builder.AppendLine("using System.Collections.Generic;"); + builder.AppendLine(); + builder.AppendLine("namespace ObsWebSocket.Core.Serialization;"); + builder.AppendLine(); + builder.AppendLine("/// "); + builder.AppendLine( + "/// The field names OBS sends for each response record, used to reject a payload being" + ); + builder.AppendLine("/// read as a record it did not come from."); + builder.AppendLine("/// "); + builder.AppendLine("internal static class ObsWebSocketPayloadSchema"); + builder.AppendLine("{"); + builder.AppendLine( + " private static readonly Dictionary s_keys = new(StringComparer.Ordinal)" + ); + builder.AppendLine(" {"); + + foreach (RequestDefinition reqDef in protocol.Requests) + { + List fields = reqDef.ResponseFields ?? []; + if (fields.Count == 0) + { + continue; + } + + // Nested fields arrive as "parent.child"; only the outermost name is a map key. + HashSet keys = new(StringComparer.Ordinal); + foreach (FieldDefinition field in fields) + { + string name = field.ValueName; + int dot = name.IndexOf('.'); + _ = keys.Add(dot >= 0 ? name.Substring(0, dot) : name); + } + + string recordName = $"{SanitizeIdentifier(reqDef.RequestType)}ResponseData"; + string list = string.Join( + ", ", + keys.OrderBy(k => k, StringComparer.Ordinal).Select(k => $"\"{k}\"") + ); + builder.AppendLine($" [\"{recordName}\"] = [{list}],"); + } + + builder.AppendLine(" };"); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine( + " /// The keys a response record expects, or an empty span when the record is unknown" + ); + builder.AppendLine(" /// to the schema, in which case no check is possible."); + builder.AppendLine(" /// "); + builder.AppendLine( + " /// The record's type name." + ); + builder.AppendLine(" public static string[] KnownKeys(string responseTypeName) =>"); + builder.AppendLine( + " s_keys.TryGetValue(responseTypeName, out string[]? keys) ? keys : [];" + ); + builder.AppendLine("}"); + + context.AddSource( + "ObsWebSocketPayloadSchema.g.cs", + SourceText.From(builder.ToString(), Encoding.UTF8) + ); + } +} diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs index 2c05508..bd62e0a 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs @@ -46,7 +46,7 @@ ProtocolDefinition protocol "/// Contains generated helper methods for the ." ); builder.AppendLine("/// "); - builder.AppendLine("public static partial class ObsWebSocketClientHelpers"); + builder.AppendLine("public static partial class ObsWebSocketClientOperations"); builder.AppendLine("{"); // Generate the WaitForEventAsync method signature and documentation @@ -253,7 +253,9 @@ ProtocolDefinition protocol ); builder.AppendLine(" tcs.TrySetCanceled(linkedCts.Token);"); builder.AppendLine( - " throw new TimeoutException($\"Timed out after {timeout} waiting for {typeof(TEventArgs).Name}.\");" + // The library's own timeout type, so catching ObsWebSocketException covers a wait + // that timed out as well as a request that did. + " throw new ObsWebSocketTimeoutException($\"Timed out after {timeout} waiting for {typeof(TEventArgs).Name}.\");" ); builder.AppendLine(" }"); builder.AppendLine(" catch (Exception ex)"); @@ -283,7 +285,7 @@ ProtocolDefinition protocol builder.AppendLine(" }"); // End WaitForEventAsync method // Close class and namespace - builder.AppendLine("}"); // End ObsWebSocketClientHelpers class + builder.AppendLine("}"); // End ObsWebSocketClientOperations class // File-scoped namespace is assumed, no closing brace needed here // Add the generated source file to the compilation diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs index f05d358..9b7aa0b 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs @@ -338,6 +338,16 @@ private static string SnakeToPascalCase(string upperSnake) => string prefix = FindCommonMemberPrefix([.. members.Select(m => m.Member)]); + // The first member is the enum's zero value, which is what an unrecognised wire value + // falls back to. Both protocol enums name theirs sensibly (Unknown, None). + string firstMember = members[0].Member; + if (prefix.Length > 0 && firstMember.StartsWith(prefix, StringComparison.Ordinal)) + { + firstMember = firstMember.Substring(prefix.Length); + } + + string fallbackMember = SnakeToPascalCase(firstMember); + StringBuilder builder = BuildSourceHeader( "// Type: Typed Enum for a string-valued protocol enum" ); @@ -427,9 +437,74 @@ private static string SnakeToPascalCase(string upperSnake) => builder.AppendLine(" _ => null,"); builder.AppendLine(" };"); builder.AppendLine("}"); + builder.AppendLine(); + AppendWireConverters(builder, enumName, fallbackMember); return builder.ToString(); } + /// + /// Emits the converters that let a generated property be the enum itself rather than the wire + /// string, one for each transport. + /// + /// + /// Both map an unrecognised value onto the enum's zero member rather than throwing, so a state + /// added by a newer OBS degrades to an unknown value instead of failing the whole message. + /// + private static void AppendWireConverters( + StringBuilder builder, + string enumName, + string fallbackMember + ) + { + builder.AppendLine("/// "); + builder.AppendLine( + $"/// Reads and writes as the protocol string in JSON." + ); + builder.AppendLine("/// "); + builder.AppendLine( + $"public sealed class {enumName}JsonConverter : System.Text.Json.Serialization.JsonConverter<{enumName}>" + ); + builder.AppendLine("{"); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public override {enumName} Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) =>" + ); + builder.AppendLine( + $" {enumName}Extensions.FromWireValue(reader.GetString()) ?? {enumName}.{fallbackMember};" + ); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public override void Write(System.Text.Json.Utf8JsonWriter writer, {enumName} value, System.Text.Json.JsonSerializerOptions options) =>" + ); + builder.AppendLine(" writer.WriteStringValue(value.ToWireValue());"); + builder.AppendLine("}"); + builder.AppendLine(); + builder.AppendLine("/// "); + builder.AppendLine( + $"/// Reads and writes as the protocol string in MessagePack." + ); + builder.AppendLine("/// "); + builder.AppendLine( + $"public sealed class {enumName}MessagePackFormatter : MessagePack.Formatters.IMessagePackFormatter<{enumName}>" + ); + builder.AppendLine("{"); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public {enumName} Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) =>" + ); + builder.AppendLine( + $" {enumName}Extensions.FromWireValue(reader.ReadString()) ?? {enumName}.{fallbackMember};" + ); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public void Serialize(ref MessagePack.MessagePackWriter writer, {enumName} value, MessagePack.MessagePackSerializerOptions options) =>" + ); + builder.AppendLine(" writer.Write(value.ToWireValue());"); + builder.AppendLine("}"); + } + /// /// Handles the case where the enum value kind could not be determined. Reports an error. /// diff --git a/ObsWebSocket.Codegen.Tasks/Generation/ProtocolCodeGenerator.cs b/ObsWebSocket.Codegen.Tasks/Generation/ProtocolCodeGenerator.cs index 65192a7..edd2006 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/ProtocolCodeGenerator.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/ProtocolCodeGenerator.cs @@ -40,6 +40,7 @@ IReadOnlyList Diagnostics Emitter.GenerateEnums(context, protocol); Emitter.GenerateRequestDtos(context, protocol); Emitter.GenerateResponseDtos(context, protocol); + Emitter.GeneratePayloadSchema(context, protocol); Emitter.GenerateClientExtensions(context, protocol); Emitter.GenerateEventPayloads(context, protocol); Emitter.GenerateEventArgs(context, protocol); diff --git a/ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs b/ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs new file mode 100644 index 0000000..836c6f0 --- /dev/null +++ b/ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs @@ -0,0 +1,32 @@ +// ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs +namespace ObsWebSocket.Codegen.Tasks.Generation; + +/// +/// Maps protocol fields that carry one of the string-valued protocol enums onto that enum, so the +/// generated property is the enum rather than a string the caller has to convert. +/// +/// +/// The definition types these fields as plain String and never says which enum they draw +/// from, so the association is written out here. Only fields backed by an enum the protocol +/// actually declares are listed: mediaState, monitorType, sceneItemBlendMode +/// and inputKind also carry fixed vocabularies, but the protocol declares no enum for them, +/// so they stay strings rather than being given one this library would have to maintain by hand. +/// +internal static class StringEnumFieldTable +{ + private static readonly Dictionary s_fieldToEnum = new(StringComparer.Ordinal) + { + // StreamStateChanged, RecordStateChanged, ReplayBufferStateChanged, VirtualcamStateChanged + ["outputState"] = "OutputState", + // TriggerMediaInputAction, and the event it raises + ["mediaAction"] = "MediaInputAction", + }; + + /// + /// Returns the enum type name a String field maps to, or when it + /// is an ordinary string. + /// + /// The protocol field name, matched case sensitively. + public static string? MapStringEnum(string fieldName) => + s_fieldToEnum.TryGetValue(fieldName, out string? enumName) ? enumName : null; +} diff --git a/ObsWebSocket.Core/BatchResultExtensions.cs b/ObsWebSocket.Core/BatchResultExtensions.cs index c7e40c3..9c9e1ad 100644 --- a/ObsWebSocket.Core/BatchResultExtensions.cs +++ b/ObsWebSocket.Core/BatchResultExtensions.cs @@ -45,6 +45,8 @@ public static class BatchResultExtensions return already; } + PayloadShape.EnsurePlausible(result.ResponseData); + // The MessagePack transport hands back the raw payload bytes, the JSON transport a // JsonElement, so a batch result has to be read according to which produced it. if (result.ResponseData is ReadOnlyMemory packed) diff --git a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.EventStreams.g.cs b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.EventStreams.g.cs index ace3b09..89894d1 100644 --- a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.EventStreams.g.cs +++ b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.EventStreams.g.cs @@ -18,6 +18,17 @@ namespace ObsWebSocket.Core; /// public readonly partial struct CanvasesGroup { + /// + /// Occurs when OBS raises CanvasCreated. + /// A new canvas has been created. + /// + /// Requires the Canvases subscription. + public event EventHandler? CanvasCreated + { + add => client.CanvasCreated += value; + remove => client.CanvasCreated -= value; + } + /// /// Streams CanvasCreated events as they arrive. /// A new canvas has been created. @@ -37,6 +48,17 @@ public readonly partial struct CanvasesGroup cancellationToken); } + /// + /// Occurs when OBS raises CanvasRemoved. + /// A canvas has been removed. + /// + /// Requires the Canvases subscription. + public event EventHandler? CanvasRemoved + { + add => client.CanvasRemoved += value; + remove => client.CanvasRemoved -= value; + } + /// /// Streams CanvasRemoved events as they arrive. /// A canvas has been removed. @@ -56,6 +78,17 @@ public readonly partial struct CanvasesGroup cancellationToken); } + /// + /// Occurs when OBS raises CanvasNameChanged. + /// The name of a canvas has changed. + /// + /// Requires the Canvases subscription. + public event EventHandler? CanvasNameChanged + { + add => client.CanvasNameChanged += value; + remove => client.CanvasNameChanged -= value; + } + /// /// Streams CanvasNameChanged events as they arrive. /// The name of a canvas has changed. @@ -84,6 +117,17 @@ public readonly partial struct CanvasesGroup /// public readonly partial struct ConfigGroup { + /// + /// Occurs when OBS raises CurrentSceneCollectionChanging. + /// The current scene collection has begun changing. Note: We recommend using this event to trigger a pause of all polling requests, as performing any requests during a scene collection change is considered undefined behavior and can cause crashes! + /// + /// Requires the Config subscription. + public event EventHandler? CurrentSceneCollectionChanging + { + add => client.CurrentSceneCollectionChanging += value; + remove => client.CurrentSceneCollectionChanging -= value; + } + /// /// Streams CurrentSceneCollectionChanging events as they arrive. /// The current scene collection has begun changing. Note: We recommend using this event to trigger a pause of all polling requests, as performing any requests during a scene collection change is considered undefined behavior and can cause crashes! @@ -103,6 +147,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentSceneCollectionChanged. + /// The current scene collection has changed. Note: If polling has been paused during `CurrentSceneCollectionChanging`, this is the que to restart polling. + /// + /// Requires the Config subscription. + public event EventHandler? CurrentSceneCollectionChanged + { + add => client.CurrentSceneCollectionChanged += value; + remove => client.CurrentSceneCollectionChanged -= value; + } + /// /// Streams CurrentSceneCollectionChanged events as they arrive. /// The current scene collection has changed. Note: If polling has been paused during `CurrentSceneCollectionChanging`, this is the que to restart polling. @@ -122,6 +177,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneCollectionListChanged. + /// The scene collection list has changed. + /// + /// Requires the Config subscription. + public event EventHandler? SceneCollectionListChanged + { + add => client.SceneCollectionListChanged += value; + remove => client.SceneCollectionListChanged -= value; + } + /// /// Streams SceneCollectionListChanged events as they arrive. /// The scene collection list has changed. @@ -141,6 +207,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentProfileChanging. + /// The current profile has begun changing. + /// + /// Requires the Config subscription. + public event EventHandler? CurrentProfileChanging + { + add => client.CurrentProfileChanging += value; + remove => client.CurrentProfileChanging -= value; + } + /// /// Streams CurrentProfileChanging events as they arrive. /// The current profile has begun changing. @@ -160,6 +237,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentProfileChanged. + /// The current profile has changed. + /// + /// Requires the Config subscription. + public event EventHandler? CurrentProfileChanged + { + add => client.CurrentProfileChanged += value; + remove => client.CurrentProfileChanged -= value; + } + /// /// Streams CurrentProfileChanged events as they arrive. /// The current profile has changed. @@ -179,6 +267,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises ProfileListChanged. + /// The profile list has changed. + /// + /// Requires the Config subscription. + public event EventHandler? ProfileListChanged + { + add => client.ProfileListChanged += value; + remove => client.ProfileListChanged -= value; + } + /// /// Streams ProfileListChanged events as they arrive. /// The profile list has changed. @@ -207,6 +306,17 @@ public readonly partial struct ConfigGroup /// public readonly partial struct FiltersGroup { + /// + /// Occurs when OBS raises SourceFilterListReindexed. + /// A source's filter list has been reindexed. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterListReindexed + { + add => client.SourceFilterListReindexed += value; + remove => client.SourceFilterListReindexed -= value; + } + /// /// Streams SourceFilterListReindexed events as they arrive. /// A source's filter list has been reindexed. @@ -226,6 +336,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterCreated. + /// A filter has been added to a source. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterCreated + { + add => client.SourceFilterCreated += value; + remove => client.SourceFilterCreated -= value; + } + /// /// Streams SourceFilterCreated events as they arrive. /// A filter has been added to a source. @@ -245,6 +366,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterRemoved. + /// A filter has been removed from a source. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterRemoved + { + add => client.SourceFilterRemoved += value; + remove => client.SourceFilterRemoved -= value; + } + /// /// Streams SourceFilterRemoved events as they arrive. /// A filter has been removed from a source. @@ -264,6 +396,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterNameChanged. + /// The name of a source filter has changed. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterNameChanged + { + add => client.SourceFilterNameChanged += value; + remove => client.SourceFilterNameChanged -= value; + } + /// /// Streams SourceFilterNameChanged events as they arrive. /// The name of a source filter has changed. @@ -283,6 +426,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterSettingsChanged. + /// An source filter's settings have changed (been updated). + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterSettingsChanged + { + add => client.SourceFilterSettingsChanged += value; + remove => client.SourceFilterSettingsChanged -= value; + } + /// /// Streams SourceFilterSettingsChanged events as they arrive. /// An source filter's settings have changed (been updated). @@ -302,6 +456,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterEnableStateChanged. + /// A source filter's enable state has changed. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterEnableStateChanged + { + add => client.SourceFilterEnableStateChanged += value; + remove => client.SourceFilterEnableStateChanged -= value; + } + /// /// Streams SourceFilterEnableStateChanged events as they arrive. /// A source filter's enable state has changed. @@ -330,6 +495,17 @@ public readonly partial struct FiltersGroup /// public readonly partial struct GeneralGroup { + /// + /// Occurs when OBS raises ExitStarted. + /// OBS has begun the shutdown process. + /// + /// Requires the General subscription. + public event EventHandler? ExitStarted + { + add => client.ExitStarted += value; + remove => client.ExitStarted -= value; + } + /// /// Streams ExitStarted events as they arrive. /// OBS has begun the shutdown process. @@ -349,6 +525,17 @@ public readonly partial struct GeneralGroup cancellationToken); } + /// + /// Occurs when OBS raises VendorEvent. + /// An event has been emitted from a vendor. A vendor is a unique name registered by a third-party plugin or script, which allows for custom requests and events to be added to obs-websocket. If a plugin or script implements vendor requests or events, documentation is expected to be provided with them. + /// + /// Requires the Vendors subscription. + public event EventHandler? VendorEvent + { + add => client.VendorEvent += value; + remove => client.VendorEvent -= value; + } + /// /// Streams VendorEvent events as they arrive. /// An event has been emitted from a vendor. A vendor is a unique name registered by a third-party plugin or script, which allows for custom requests and events to be added to obs-websocket. If a plugin or script implements vendor requests or events, documentation is expected to be provided with them. @@ -368,6 +555,17 @@ public readonly partial struct GeneralGroup cancellationToken); } + /// + /// Occurs when OBS raises CustomEvent. + /// Custom event emitted by `BroadcastCustomEvent`. + /// + /// Requires the General subscription. + public event EventHandler? CustomEvent + { + add => client.CustomEvent += value; + remove => client.CustomEvent -= value; + } + /// /// Streams CustomEvent events as they arrive. /// Custom event emitted by `BroadcastCustomEvent`. @@ -396,6 +594,17 @@ public readonly partial struct GeneralGroup /// public readonly partial struct InputsGroup { + /// + /// Occurs when OBS raises InputCreated. + /// An input has been created. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputCreated + { + add => client.InputCreated += value; + remove => client.InputCreated -= value; + } + /// /// Streams InputCreated events as they arrive. /// An input has been created. @@ -415,6 +624,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputRemoved. + /// An input has been removed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputRemoved + { + add => client.InputRemoved += value; + remove => client.InputRemoved -= value; + } + /// /// Streams InputRemoved events as they arrive. /// An input has been removed. @@ -434,6 +654,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputNameChanged. + /// The name of an input has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputNameChanged + { + add => client.InputNameChanged += value; + remove => client.InputNameChanged -= value; + } + /// /// Streams InputNameChanged events as they arrive. /// The name of an input has changed. @@ -453,6 +684,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputSettingsChanged. + /// An input's settings have changed (been updated). Note: On some inputs, changing values in the properties dialog will cause an immediate update. Pressing the "Cancel" button will revert the settings, resulting in another event being fired. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputSettingsChanged + { + add => client.InputSettingsChanged += value; + remove => client.InputSettingsChanged -= value; + } + /// /// Streams InputSettingsChanged events as they arrive. /// An input's settings have changed (been updated). Note: On some inputs, changing values in the properties dialog will cause an immediate update. Pressing the "Cancel" button will revert the settings, resulting in another event being fired. @@ -472,6 +714,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputActiveStateChanged. + /// An input's active state has changed. When an input is active, it means it's being shown by the program feed. + /// + /// Requires the InputActiveStateChanged subscription. + public event EventHandler? InputActiveStateChanged + { + add => client.InputActiveStateChanged += value; + remove => client.InputActiveStateChanged -= value; + } + /// /// Streams InputActiveStateChanged events as they arrive. /// An input's active state has changed. When an input is active, it means it's being shown by the program feed. @@ -491,6 +744,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputShowStateChanged. + /// An input's show state has changed. When an input is showing, it means it's being shown by the preview or a dialog. + /// + /// Requires the InputShowStateChanged subscription. + public event EventHandler? InputShowStateChanged + { + add => client.InputShowStateChanged += value; + remove => client.InputShowStateChanged -= value; + } + /// /// Streams InputShowStateChanged events as they arrive. /// An input's show state has changed. When an input is showing, it means it's being shown by the preview or a dialog. @@ -510,6 +774,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputMuteStateChanged. + /// An input's mute state has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputMuteStateChanged + { + add => client.InputMuteStateChanged += value; + remove => client.InputMuteStateChanged -= value; + } + /// /// Streams InputMuteStateChanged events as they arrive. /// An input's mute state has changed. @@ -529,6 +804,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputVolumeChanged. + /// An input's volume level has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputVolumeChanged + { + add => client.InputVolumeChanged += value; + remove => client.InputVolumeChanged -= value; + } + /// /// Streams InputVolumeChanged events as they arrive. /// An input's volume level has changed. @@ -548,6 +834,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputAudioBalanceChanged. + /// The audio balance value of an input has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputAudioBalanceChanged + { + add => client.InputAudioBalanceChanged += value; + remove => client.InputAudioBalanceChanged -= value; + } + /// /// Streams InputAudioBalanceChanged events as they arrive. /// The audio balance value of an input has changed. @@ -567,6 +864,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputAudioSyncOffsetChanged. + /// The sync offset of an input has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputAudioSyncOffsetChanged + { + add => client.InputAudioSyncOffsetChanged += value; + remove => client.InputAudioSyncOffsetChanged -= value; + } + /// /// Streams InputAudioSyncOffsetChanged events as they arrive. /// The sync offset of an input has changed. @@ -586,6 +894,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputAudioTracksChanged. + /// The audio tracks of an input have changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputAudioTracksChanged + { + add => client.InputAudioTracksChanged += value; + remove => client.InputAudioTracksChanged -= value; + } + /// /// Streams InputAudioTracksChanged events as they arrive. /// The audio tracks of an input have changed. @@ -605,6 +924,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputAudioMonitorTypeChanged. + /// The monitor type of an input has changed. Available types are: - `OBS_MONITORING_TYPE_NONE` - `OBS_MONITORING_TYPE_MONITOR_ONLY` - `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT` + /// + /// Requires the Inputs subscription. + public event EventHandler? InputAudioMonitorTypeChanged + { + add => client.InputAudioMonitorTypeChanged += value; + remove => client.InputAudioMonitorTypeChanged -= value; + } + /// /// Streams InputAudioMonitorTypeChanged events as they arrive. /// The monitor type of an input has changed. Available types are: - `OBS_MONITORING_TYPE_NONE` - `OBS_MONITORING_TYPE_MONITOR_ONLY` - `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT` @@ -624,6 +954,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputVolumeMeters. + /// A high-volume event providing volume levels of all active inputs every 50 milliseconds. + /// + /// Requires the InputVolumeMeters subscription. + public event EventHandler? InputVolumeMeters + { + add => client.InputVolumeMeters += value; + remove => client.InputVolumeMeters -= value; + } + /// /// Streams InputVolumeMeters events as they arrive. /// A high-volume event providing volume levels of all active inputs every 50 milliseconds. @@ -652,6 +993,17 @@ public readonly partial struct InputsGroup /// public readonly partial struct MediaInputsGroup { + /// + /// Occurs when OBS raises MediaInputPlaybackStarted. + /// A media input has started playing. + /// + /// Requires the MediaInputs subscription. + public event EventHandler? MediaInputPlaybackStarted + { + add => client.MediaInputPlaybackStarted += value; + remove => client.MediaInputPlaybackStarted -= value; + } + /// /// Streams MediaInputPlaybackStarted events as they arrive. /// A media input has started playing. @@ -671,6 +1023,17 @@ public readonly partial struct MediaInputsGroup cancellationToken); } + /// + /// Occurs when OBS raises MediaInputPlaybackEnded. + /// A media input has finished playing. + /// + /// Requires the MediaInputs subscription. + public event EventHandler? MediaInputPlaybackEnded + { + add => client.MediaInputPlaybackEnded += value; + remove => client.MediaInputPlaybackEnded -= value; + } + /// /// Streams MediaInputPlaybackEnded events as they arrive. /// A media input has finished playing. @@ -690,6 +1053,17 @@ public readonly partial struct MediaInputsGroup cancellationToken); } + /// + /// Occurs when OBS raises MediaInputActionTriggered. + /// An action has been performed on an input. + /// + /// Requires the MediaInputs subscription. + public event EventHandler? MediaInputActionTriggered + { + add => client.MediaInputActionTriggered += value; + remove => client.MediaInputActionTriggered -= value; + } + /// /// Streams MediaInputActionTriggered events as they arrive. /// An action has been performed on an input. @@ -718,6 +1092,17 @@ public readonly partial struct MediaInputsGroup /// public readonly partial struct OutputsGroup { + /// + /// Occurs when OBS raises StreamStateChanged. + /// The state of the stream output has changed. + /// + /// Requires the Outputs subscription. + public event EventHandler? StreamStateChanged + { + add => client.StreamStateChanged += value; + remove => client.StreamStateChanged -= value; + } + /// /// Streams StreamStateChanged events as they arrive. /// The state of the stream output has changed. @@ -737,6 +1122,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises RecordStateChanged. + /// The state of the record output has changed. + /// + /// Requires the Outputs subscription. + public event EventHandler? RecordStateChanged + { + add => client.RecordStateChanged += value; + remove => client.RecordStateChanged -= value; + } + /// /// Streams RecordStateChanged events as they arrive. /// The state of the record output has changed. @@ -756,6 +1152,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises RecordFileChanged. + /// The record output has started writing to a new file. For example, when a file split happens. + /// + /// Requires the Outputs subscription. + public event EventHandler? RecordFileChanged + { + add => client.RecordFileChanged += value; + remove => client.RecordFileChanged -= value; + } + /// /// Streams RecordFileChanged events as they arrive. /// The record output has started writing to a new file. For example, when a file split happens. @@ -775,6 +1182,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises ReplayBufferStateChanged. + /// The state of the replay buffer output has changed. + /// + /// Requires the Outputs subscription. + public event EventHandler? ReplayBufferStateChanged + { + add => client.ReplayBufferStateChanged += value; + remove => client.ReplayBufferStateChanged -= value; + } + /// /// Streams ReplayBufferStateChanged events as they arrive. /// The state of the replay buffer output has changed. @@ -794,6 +1212,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises VirtualcamStateChanged. + /// The state of the virtualcam output has changed. + /// + /// Requires the Outputs subscription. + public event EventHandler? VirtualcamStateChanged + { + add => client.VirtualcamStateChanged += value; + remove => client.VirtualcamStateChanged -= value; + } + /// /// Streams VirtualcamStateChanged events as they arrive. /// The state of the virtualcam output has changed. @@ -813,6 +1242,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises ReplayBufferSaved. + /// The replay buffer has been saved. + /// + /// Requires the Outputs subscription. + public event EventHandler? ReplayBufferSaved + { + add => client.ReplayBufferSaved += value; + remove => client.ReplayBufferSaved -= value; + } + /// /// Streams ReplayBufferSaved events as they arrive. /// The replay buffer has been saved. @@ -841,6 +1281,17 @@ public readonly partial struct OutputsGroup /// public readonly partial struct SceneItemsGroup { + /// + /// Occurs when OBS raises SceneItemCreated. + /// A scene item has been created. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemCreated + { + add => client.SceneItemCreated += value; + remove => client.SceneItemCreated -= value; + } + /// /// Streams SceneItemCreated events as they arrive. /// A scene item has been created. @@ -860,6 +1311,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemRemoved. + /// A scene item has been removed. This event is not emitted when the scene the item is in is removed. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemRemoved + { + add => client.SceneItemRemoved += value; + remove => client.SceneItemRemoved -= value; + } + /// /// Streams SceneItemRemoved events as they arrive. /// A scene item has been removed. This event is not emitted when the scene the item is in is removed. @@ -879,6 +1341,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemListReindexed. + /// A scene's item list has been reindexed. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemListReindexed + { + add => client.SceneItemListReindexed += value; + remove => client.SceneItemListReindexed -= value; + } + /// /// Streams SceneItemListReindexed events as they arrive. /// A scene's item list has been reindexed. @@ -898,6 +1371,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemEnableStateChanged. + /// A scene item's enable state has changed. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemEnableStateChanged + { + add => client.SceneItemEnableStateChanged += value; + remove => client.SceneItemEnableStateChanged -= value; + } + /// /// Streams SceneItemEnableStateChanged events as they arrive. /// A scene item's enable state has changed. @@ -917,6 +1401,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemLockStateChanged. + /// A scene item's lock state has changed. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemLockStateChanged + { + add => client.SceneItemLockStateChanged += value; + remove => client.SceneItemLockStateChanged -= value; + } + /// /// Streams SceneItemLockStateChanged events as they arrive. /// A scene item's lock state has changed. @@ -936,6 +1431,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemSelected. + /// A scene item has been selected in the Ui. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemSelected + { + add => client.SceneItemSelected += value; + remove => client.SceneItemSelected -= value; + } + /// /// Streams SceneItemSelected events as they arrive. /// A scene item has been selected in the Ui. @@ -955,6 +1461,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemTransformChanged. + /// The transform/crop of a scene item has changed. + /// + /// Requires the SceneItemTransformChanged subscription. + public event EventHandler? SceneItemTransformChanged + { + add => client.SceneItemTransformChanged += value; + remove => client.SceneItemTransformChanged -= value; + } + /// /// Streams SceneItemTransformChanged events as they arrive. /// The transform/crop of a scene item has changed. @@ -983,6 +1500,17 @@ public readonly partial struct SceneItemsGroup /// public readonly partial struct ScenesGroup { + /// + /// Occurs when OBS raises SceneCreated. + /// A new scene has been created. + /// + /// Requires the Scenes subscription. + public event EventHandler? SceneCreated + { + add => client.SceneCreated += value; + remove => client.SceneCreated -= value; + } + /// /// Streams SceneCreated events as they arrive. /// A new scene has been created. @@ -1002,6 +1530,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneRemoved. + /// A scene has been removed. + /// + /// Requires the Scenes subscription. + public event EventHandler? SceneRemoved + { + add => client.SceneRemoved += value; + remove => client.SceneRemoved -= value; + } + /// /// Streams SceneRemoved events as they arrive. /// A scene has been removed. @@ -1021,6 +1560,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneNameChanged. + /// The name of a scene has changed. + /// + /// Requires the Scenes subscription. + public event EventHandler? SceneNameChanged + { + add => client.SceneNameChanged += value; + remove => client.SceneNameChanged -= value; + } + /// /// Streams SceneNameChanged events as they arrive. /// The name of a scene has changed. @@ -1040,6 +1590,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentProgramSceneChanged. + /// The current program scene has changed. + /// + /// Requires the Scenes subscription. + public event EventHandler? CurrentProgramSceneChanged + { + add => client.CurrentProgramSceneChanged += value; + remove => client.CurrentProgramSceneChanged -= value; + } + /// /// Streams CurrentProgramSceneChanged events as they arrive. /// The current program scene has changed. @@ -1059,6 +1620,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentPreviewSceneChanged. + /// The current preview scene has changed. + /// + /// Requires the Scenes subscription. + public event EventHandler? CurrentPreviewSceneChanged + { + add => client.CurrentPreviewSceneChanged += value; + remove => client.CurrentPreviewSceneChanged -= value; + } + /// /// Streams CurrentPreviewSceneChanged events as they arrive. /// The current preview scene has changed. @@ -1078,6 +1650,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneListChanged. + /// The list of scenes has changed. TODO: Make OBS fire this event when scenes are reordered. + /// + /// Requires the Scenes subscription. + public event EventHandler? SceneListChanged + { + add => client.SceneListChanged += value; + remove => client.SceneListChanged -= value; + } + /// /// Streams SceneListChanged events as they arrive. /// The list of scenes has changed. TODO: Make OBS fire this event when scenes are reordered. @@ -1106,6 +1689,17 @@ public readonly partial struct ScenesGroup /// public readonly partial struct TransitionsGroup { + /// + /// Occurs when OBS raises CurrentSceneTransitionChanged. + /// The current scene transition has changed. + /// + /// Requires the Transitions subscription. + public event EventHandler? CurrentSceneTransitionChanged + { + add => client.CurrentSceneTransitionChanged += value; + remove => client.CurrentSceneTransitionChanged -= value; + } + /// /// Streams CurrentSceneTransitionChanged events as they arrive. /// The current scene transition has changed. @@ -1125,6 +1719,17 @@ public readonly partial struct TransitionsGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentSceneTransitionDurationChanged. + /// The current scene transition duration has changed. + /// + /// Requires the Transitions subscription. + public event EventHandler? CurrentSceneTransitionDurationChanged + { + add => client.CurrentSceneTransitionDurationChanged += value; + remove => client.CurrentSceneTransitionDurationChanged -= value; + } + /// /// Streams CurrentSceneTransitionDurationChanged events as they arrive. /// The current scene transition duration has changed. @@ -1144,6 +1749,17 @@ public readonly partial struct TransitionsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneTransitionStarted. + /// A scene transition has started. + /// + /// Requires the Transitions subscription. + public event EventHandler? SceneTransitionStarted + { + add => client.SceneTransitionStarted += value; + remove => client.SceneTransitionStarted -= value; + } + /// /// Streams SceneTransitionStarted events as they arrive. /// A scene transition has started. @@ -1163,6 +1779,17 @@ public readonly partial struct TransitionsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneTransitionEnded. + /// A scene transition has completed fully. Note: Does not appear to trigger when the transition is interrupted by the user. + /// + /// Requires the Transitions subscription. + public event EventHandler? SceneTransitionEnded + { + add => client.SceneTransitionEnded += value; + remove => client.SceneTransitionEnded -= value; + } + /// /// Streams SceneTransitionEnded events as they arrive. /// A scene transition has completed fully. Note: Does not appear to trigger when the transition is interrupted by the user. @@ -1182,6 +1809,17 @@ public readonly partial struct TransitionsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneTransitionVideoEnded. + /// A scene transition's video has completed fully. Useful for stinger transitions to tell when the video *actually* ends. `SceneTransitionEnded` only signifies the cut point, not the completion of transition playback. Note: Appears to be called by every transition, regardless of relevance. + /// + /// Requires the Transitions subscription. + public event EventHandler? SceneTransitionVideoEnded + { + add => client.SceneTransitionVideoEnded += value; + remove => client.SceneTransitionVideoEnded -= value; + } + /// /// Streams SceneTransitionVideoEnded events as they arrive. /// A scene transition's video has completed fully. Useful for stinger transitions to tell when the video *actually* ends. `SceneTransitionEnded` only signifies the cut point, not the completion of transition playback. Note: Appears to be called by every transition, regardless of relevance. @@ -1210,6 +1848,17 @@ public readonly partial struct TransitionsGroup /// public readonly partial struct UiGroup { + /// + /// Occurs when OBS raises StudioModeStateChanged. + /// Studio mode has been enabled or disabled. + /// + /// Requires the Ui subscription. + public event EventHandler? StudioModeStateChanged + { + add => client.StudioModeStateChanged += value; + remove => client.StudioModeStateChanged -= value; + } + /// /// Streams StudioModeStateChanged events as they arrive. /// Studio mode has been enabled or disabled. @@ -1229,6 +1878,17 @@ public readonly partial struct UiGroup cancellationToken); } + /// + /// Occurs when OBS raises ScreenshotSaved. + /// A screenshot has been saved. Note: Triggered for the screenshot feature available in `Settings -> Hotkeys -> Screenshot Output` ONLY. Applications using `Get/SaveSourceScreenshot` should implement a `CustomEvent` if this kind of inter-client communication is desired. + /// + /// Requires the Ui subscription. + public event EventHandler? ScreenshotSaved + { + add => client.ScreenshotSaved += value; + remove => client.ScreenshotSaved -= value; + } + /// /// Streams ScreenshotSaved events as they arrive. /// A screenshot has been saved. Note: Triggered for the screenshot feature available in `Settings -> Hotkeys -> Screenshot Output` ONLY. Applications using `Get/SaveSourceScreenshot` should implement a `CustomEvent` if this kind of inter-client communication is desired. diff --git a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs index 79ea6b9..3c21fd2 100644 --- a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs +++ b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs @@ -15,7 +15,7 @@ namespace ObsWebSocket.Core; // Add to ObsWebSocket.Core namespace /// /// Contains generated helper methods for the . /// -public static partial class ObsWebSocketClientHelpers +public static partial class ObsWebSocketClientOperations { /// /// Asynchronously waits for a specific OBS event of type that satisfies a predicate condition. @@ -1320,7 +1320,7 @@ public static async Task WaitForEventAsync( { client._logger.LogDebug("WaitForEventAsync<{EventType}> timed out after {Timeout}.", typeof(TEventArgs).Name, timeout); tcs.TrySetCanceled(linkedCts.Token); - throw new TimeoutException($"Timed out after {timeout} waiting for {typeof(TEventArgs).Name}."); + throw new ObsWebSocketTimeoutException($"Timed out after {timeout} waiting for {typeof(TEventArgs).Name}."); } catch (Exception ex) { diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasCreated.EventPayload.g.cs index b9c2593..67fa8c1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasCreated.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CanvasCreatedPayload /// [JsonPropertyName("canvasName")] [Key("canvasName")] - public string? CanvasName { get; init; } + public required string CanvasName { get; init; } /// /// UUID of the new canvas /// [JsonPropertyName("canvasUuid")] [Key("canvasUuid")] - public string? CanvasUuid { get; init; } + public required string CanvasUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CanvasCreatedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CanvasCreatedPayload(string? canvasName = null, string? canvasUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CanvasCreatedPayload(string canvasName, string canvasUuid) { this.CanvasName = canvasName; this.CanvasUuid = canvasUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasNameChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasNameChanged.EventPayload.g.cs index 892daee..82dc87d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasNameChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasNameChanged.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record CanvasNameChangedPayload /// [JsonPropertyName("canvasName")] [Key("canvasName")] - public string? CanvasName { get; init; } + public required string CanvasName { get; init; } /// /// UUID of the canvas /// [JsonPropertyName("canvasUuid")] [Key("canvasUuid")] - public string? CanvasUuid { get; init; } + public required string CanvasUuid { get; init; } /// /// Old name of the canvas /// [JsonPropertyName("oldCanvasName")] [Key("oldCanvasName")] - public string? OldCanvasName { get; init; } + public required string OldCanvasName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public CanvasNameChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CanvasNameChangedPayload(string? canvasUuid = null, string? oldCanvasName = null, string? canvasName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CanvasNameChangedPayload(string canvasUuid, string oldCanvasName, string canvasName) { this.CanvasUuid = canvasUuid; this.OldCanvasName = oldCanvasName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasRemoved.EventPayload.g.cs index 871ecb0..dd9f151 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasRemoved.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CanvasRemovedPayload /// [JsonPropertyName("canvasName")] [Key("canvasName")] - public string? CanvasName { get; init; } + public required string CanvasName { get; init; } /// /// UUID of the removed canvas /// [JsonPropertyName("canvasUuid")] [Key("canvasUuid")] - public string? CanvasUuid { get; init; } + public required string CanvasUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CanvasRemovedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CanvasRemovedPayload(string? canvasName = null, string? canvasUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CanvasRemovedPayload(string canvasName, string canvasUuid) { this.CanvasName = canvasName; this.CanvasUuid = canvasUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentPreviewSceneChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentPreviewSceneChanged.EventPayload.g.cs index 5617e41..3f44b74 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentPreviewSceneChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentPreviewSceneChanged.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CurrentPreviewSceneChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene that was switched to /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CurrentPreviewSceneChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentPreviewSceneChangedPayload(string? sceneName = null, string? sceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentPreviewSceneChangedPayload(string sceneName, string sceneUuid) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanged.EventPayload.g.cs index 1c0d6b3..f426b44 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record CurrentProfileChangedPayload /// [JsonPropertyName("profileName")] [Key("profileName")] - public string? ProfileName { get; init; } + public required string ProfileName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public CurrentProfileChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentProfileChangedPayload(string? profileName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentProfileChangedPayload(string profileName) { this.ProfileName = profileName; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanging.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanging.EventPayload.g.cs index 55d23d2..75d2175 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanging.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanging.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record CurrentProfileChangingPayload /// [JsonPropertyName("profileName")] [Key("profileName")] - public string? ProfileName { get; init; } + public required string ProfileName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public CurrentProfileChangingPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentProfileChangingPayload(string? profileName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentProfileChangingPayload(string profileName) { this.ProfileName = profileName; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProgramSceneChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProgramSceneChanged.EventPayload.g.cs index 78c6388..75b86f7 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProgramSceneChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProgramSceneChanged.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CurrentProgramSceneChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene that was switched to /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CurrentProgramSceneChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentProgramSceneChangedPayload(string? sceneName = null, string? sceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentProgramSceneChangedPayload(string sceneName, string sceneUuid) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanged.EventPayload.g.cs index b9fbe31..739e6f7 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanged.EventPayload.g.cs @@ -31,7 +31,7 @@ public sealed partial record CurrentSceneCollectionChangedPayload /// [JsonPropertyName("sceneCollectionName")] [Key("sceneCollectionName")] - public string? SceneCollectionName { get; init; } + public required string SceneCollectionName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public CurrentSceneCollectionChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentSceneCollectionChangedPayload(string? sceneCollectionName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentSceneCollectionChangedPayload(string sceneCollectionName) { this.SceneCollectionName = sceneCollectionName; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanging.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanging.EventPayload.g.cs index a1b6c61..bbfd793 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanging.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanging.EventPayload.g.cs @@ -32,7 +32,7 @@ public sealed partial record CurrentSceneCollectionChangingPayload /// [JsonPropertyName("sceneCollectionName")] [Key("sceneCollectionName")] - public string? SceneCollectionName { get; init; } + public required string SceneCollectionName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -42,7 +42,8 @@ public CurrentSceneCollectionChangingPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentSceneCollectionChangingPayload(string? sceneCollectionName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentSceneCollectionChangingPayload(string sceneCollectionName) { this.SceneCollectionName = sceneCollectionName; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneTransitionChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneTransitionChanged.EventPayload.g.cs index 202876a..3cd48bf 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneTransitionChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneTransitionChanged.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CurrentSceneTransitionChangedPayload /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// UUID of the new transition /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CurrentSceneTransitionChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentSceneTransitionChangedPayload(string? transitionName = null, string? transitionUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentSceneTransitionChangedPayload(string transitionName, string transitionUuid) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CustomEvent.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CustomEvent.EventPayload.g.cs index 6540d45..3ef7478 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CustomEvent.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CustomEvent.EventPayload.g.cs @@ -39,7 +39,7 @@ public CustomEventPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CustomEventPayload(System.Text.Json.JsonElement? eventData = null) + public CustomEventPayload(System.Text.Json.JsonElement? eventData = default) { this.EventData = eventData; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputActiveStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputActiveStateChanged.EventPayload.g.cs index c4e0cea..0ae0d39 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputActiveStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputActiveStateChanged.EventPayload.g.cs @@ -31,14 +31,14 @@ public sealed partial record InputActiveStateChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// Whether the input is active @@ -56,7 +56,7 @@ public InputActiveStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputActiveStateChangedPayload(bool videoActive, string? inputName = null, string? inputUuid = null) + public InputActiveStateChangedPayload(string inputName, string inputUuid, bool videoActive) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioBalanceChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioBalanceChanged.EventPayload.g.cs index aac65ae..6c1e63c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioBalanceChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioBalanceChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record InputAudioBalanceChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public InputAudioBalanceChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputAudioBalanceChangedPayload(double inputAudioBalance, string? inputName = null, string? inputUuid = null) + public InputAudioBalanceChangedPayload(string inputName, string inputUuid, double inputAudioBalance) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioMonitorTypeChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioMonitorTypeChanged.EventPayload.g.cs index 2cf962d..cdb1e9e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioMonitorTypeChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioMonitorTypeChanged.EventPayload.g.cs @@ -35,21 +35,21 @@ public sealed partial record InputAudioMonitorTypeChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// New monitor type of the input /// [JsonPropertyName("monitorType")] [Key("monitorType")] - public string? MonitorType { get; init; } + public required string MonitorType { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -59,7 +59,8 @@ public InputAudioMonitorTypeChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputAudioMonitorTypeChangedPayload(string? inputName = null, string? inputUuid = null, string? monitorType = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputAudioMonitorTypeChangedPayload(string inputName, string inputUuid, string monitorType) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioSyncOffsetChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioSyncOffsetChanged.EventPayload.g.cs index 027ebf4..6355bd2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioSyncOffsetChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioSyncOffsetChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record InputAudioSyncOffsetChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public InputAudioSyncOffsetChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputAudioSyncOffsetChangedPayload(int inputAudioSyncOffset, string? inputName = null, string? inputUuid = null) + public InputAudioSyncOffsetChangedPayload(string inputName, string inputUuid, int inputAudioSyncOffset) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs index 560c00b..0250260 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record InputAudioTracksChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public InputAudioTracksChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputAudioTracksChangedPayload(string? inputName = null, string? inputUuid = null, System.Collections.Generic.Dictionary? inputAudioTracks = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputAudioTracksChangedPayload(string inputName, string inputUuid, System.Collections.Generic.Dictionary? inputAudioTracks = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs index a57cb9d..5f098e3 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs @@ -36,7 +36,7 @@ public sealed partial record InputCreatedPayload /// [JsonPropertyName("inputKind")] [Key("inputKind")] - public string? InputKind { get; init; } + public required string InputKind { get; init; } /// /// Bitflag value for the caps that an input supports. See obs_source_info.output_flags in the libobs docs @@ -50,7 +50,7 @@ public sealed partial record InputCreatedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// The settings configured to the input when it was created @@ -64,14 +64,14 @@ public sealed partial record InputCreatedPayload /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// The unversioned kind of input (aka no `_v2` stuff) /// [JsonPropertyName("unversionedInputKind")] [Key("unversionedInputKind")] - public string? UnversionedInputKind { get; init; } + public required string UnversionedInputKind { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -82,7 +82,7 @@ public InputCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputCreatedPayload(long inputKindCaps, string? inputName = null, string? inputUuid = null, string? inputKind = null, string? unversionedInputKind = null, System.Text.Json.JsonElement? inputSettings = null, System.Text.Json.JsonElement? defaultInputSettings = null) + public InputCreatedPayload(string inputName, string inputUuid, string inputKind, string unversionedInputKind, long inputKindCaps, System.Text.Json.JsonElement? inputSettings = default, System.Text.Json.JsonElement? defaultInputSettings = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputMuteStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputMuteStateChanged.EventPayload.g.cs index 9af23b7..fae8006 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputMuteStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputMuteStateChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record InputMuteStateChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public InputMuteStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputMuteStateChangedPayload(bool inputMuted, string? inputName = null, string? inputUuid = null) + public InputMuteStateChangedPayload(string inputName, string inputUuid, bool inputMuted) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputNameChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputNameChanged.EventPayload.g.cs index fb8163b..e305c8e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputNameChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputNameChanged.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record InputNameChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// Old name of the input /// [JsonPropertyName("oldInputName")] [Key("oldInputName")] - public string? OldInputName { get; init; } + public required string OldInputName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public InputNameChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputNameChangedPayload(string? inputUuid = null, string? oldInputName = null, string? inputName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputNameChangedPayload(string inputUuid, string oldInputName, string inputName) { this.InputUuid = inputUuid; this.OldInputName = oldInputName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputRemoved.EventPayload.g.cs index c433e84..5512e96 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputRemoved.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record InputRemovedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public InputRemovedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputRemovedPayload(string? inputName = null, string? inputUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputRemovedPayload(string inputName, string inputUuid) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs index 1cbd118..68fd0f6 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs @@ -31,7 +31,7 @@ public sealed partial record InputSettingsChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// New settings object of the input @@ -45,7 +45,7 @@ public sealed partial record InputSettingsChangedPayload /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -55,7 +55,8 @@ public InputSettingsChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputSettingsChangedPayload(string? inputName = null, string? inputUuid = null, System.Text.Json.JsonElement? inputSettings = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputSettingsChangedPayload(string inputName, string inputUuid, System.Text.Json.JsonElement? inputSettings = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputShowStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputShowStateChanged.EventPayload.g.cs index ceb21db..59def4d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputShowStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputShowStateChanged.EventPayload.g.cs @@ -31,14 +31,14 @@ public sealed partial record InputShowStateChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// Whether the input is showing @@ -56,7 +56,7 @@ public InputShowStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputShowStateChangedPayload(bool videoShowing, string? inputName = null, string? inputUuid = null) + public InputShowStateChangedPayload(string inputName, string inputUuid, bool videoShowing) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeChanged.EventPayload.g.cs index 677cdfb..0dad780 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeChanged.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record InputVolumeChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// New volume level in dB @@ -61,7 +61,7 @@ public InputVolumeChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputVolumeChangedPayload(double inputVolumeMul, double inputVolumeDb, string? inputName = null, string? inputUuid = null) + public InputVolumeChangedPayload(string inputName, string inputUuid, double inputVolumeMul, double inputVolumeDb) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs index 1c231ca..35bb10f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record InputVolumeMetersPayload /// [JsonPropertyName("inputs")] [Key("inputs")] - public System.Collections.Generic.List? Inputs { get; init; } + public required System.Collections.Generic.List Inputs { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public InputVolumeMetersPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputVolumeMetersPayload(System.Collections.Generic.List? inputs = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputVolumeMetersPayload(System.Collections.Generic.List inputs) { this.Inputs = inputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs index c279ad1..2172bf3 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs @@ -29,21 +29,23 @@ public sealed partial record MediaInputActionTriggeredPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// Action performed on the input. See `ObsMediaInputAction` enum /// [JsonPropertyName("mediaAction")] [Key("mediaAction")] - public string? MediaAction { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.MediaInputActionJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.MediaInputActionMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.MediaInputAction MediaAction { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +55,8 @@ public MediaInputActionTriggeredPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public MediaInputActionTriggeredPayload(string? inputName = null, string? inputUuid = null, string? mediaAction = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public MediaInputActionTriggeredPayload(string inputName, string inputUuid, ObsWebSocket.Core.Protocol.Generated.MediaInputAction mediaAction = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackEnded.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackEnded.EventPayload.g.cs index aa100b7..20e0076 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackEnded.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackEnded.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record MediaInputPlaybackEndedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public MediaInputPlaybackEndedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public MediaInputPlaybackEndedPayload(string? inputName = null, string? inputUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public MediaInputPlaybackEndedPayload(string inputName, string inputUuid) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackStarted.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackStarted.EventPayload.g.cs index 0fe00ba..ef8b3cf 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackStarted.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackStarted.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record MediaInputPlaybackStartedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public MediaInputPlaybackStartedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public MediaInputPlaybackStartedPayload(string? inputName = null, string? inputUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public MediaInputPlaybackStartedPayload(string inputName, string inputUuid) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs index 071862f..efd2f8e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record ProfileListChangedPayload /// [JsonPropertyName("profiles")] [Key("profiles")] - public System.Collections.Generic.List? Profiles { get; init; } + public required System.Collections.Generic.List Profiles { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public ProfileListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public ProfileListChangedPayload(System.Collections.Generic.List? profiles = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public ProfileListChangedPayload(System.Collections.Generic.List profiles) { this.Profiles = profiles; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/RecordFileChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/RecordFileChanged.EventPayload.g.cs index 21dfe13..f07709b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/RecordFileChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/RecordFileChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record RecordFileChangedPayload /// [JsonPropertyName("newOutputPath")] [Key("newOutputPath")] - public string? NewOutputPath { get; init; } + public required string NewOutputPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public RecordFileChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public RecordFileChangedPayload(string? newOutputPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public RecordFileChangedPayload(string newOutputPath) { this.NewOutputPath = newOutputPath; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/RecordStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/RecordStateChanged.EventPayload.g.cs index c255a07..d931b31 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/RecordStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/RecordStateChanged.EventPayload.g.cs @@ -43,7 +43,9 @@ public sealed partial record RecordStateChangedPayload /// [JsonPropertyName("outputState")] [Key("outputState")] - public string? OutputState { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.OutputState OutputState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +56,7 @@ public RecordStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public RecordStateChangedPayload(bool outputActive, string? outputState = null, string? outputPath = null) + public RecordStateChangedPayload(bool outputActive, ObsWebSocket.Core.Protocol.Generated.OutputState outputState = default, string? outputPath = null) { this.OutputActive = outputActive; this.OutputState = outputState; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferSaved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferSaved.EventPayload.g.cs index 0eaa21f..83bbcda 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferSaved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferSaved.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record ReplayBufferSavedPayload /// [JsonPropertyName("savedReplayPath")] [Key("savedReplayPath")] - public string? SavedReplayPath { get; init; } + public required string SavedReplayPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public ReplayBufferSavedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public ReplayBufferSavedPayload(string? savedReplayPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public ReplayBufferSavedPayload(string savedReplayPath) { this.SavedReplayPath = savedReplayPath; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferStateChanged.EventPayload.g.cs index 4024dc7..5dae271 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferStateChanged.EventPayload.g.cs @@ -36,7 +36,9 @@ public sealed partial record ReplayBufferStateChangedPayload /// [JsonPropertyName("outputState")] [Key("outputState")] - public string? OutputState { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.OutputState OutputState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -47,7 +49,7 @@ public ReplayBufferStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public ReplayBufferStateChangedPayload(bool outputActive, string? outputState = null) + public ReplayBufferStateChangedPayload(bool outputActive, ObsWebSocket.Core.Protocol.Generated.OutputState outputState = default) { this.OutputActive = outputActive; this.OutputState = outputState; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs index c3d41de..274ed52 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record SceneCollectionListChangedPayload /// [JsonPropertyName("sceneCollections")] [Key("sceneCollections")] - public System.Collections.Generic.List? SceneCollections { get; init; } + public required System.Collections.Generic.List SceneCollections { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public SceneCollectionListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneCollectionListChangedPayload(System.Collections.Generic.List? sceneCollections = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneCollectionListChangedPayload(System.Collections.Generic.List sceneCollections) { this.SceneCollections = sceneCollections; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCreated.EventPayload.g.cs index 096fb2b..3843803 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCreated.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record SceneCreatedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the new scene /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public SceneCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneCreatedPayload(bool isGroup, string? sceneName = null, string? sceneUuid = null) + public SceneCreatedPayload(string sceneName, string sceneUuid, bool isGroup) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemCreated.EventPayload.g.cs index 76dad2f..406822e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemCreated.EventPayload.g.cs @@ -43,28 +43,28 @@ public sealed partial record SceneItemCreatedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item was added to /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// /// Name of the underlying source (input/scene) /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// /// UUID of the underlying source (input/scene) /// [JsonPropertyName("sourceUuid")] [Key("sourceUuid")] - public string? SourceUuid { get; init; } + public required string SourceUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -75,7 +75,7 @@ public SceneItemCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemCreatedPayload(int sceneItemId, int sceneItemIndex, string? sceneName = null, string? sceneUuid = null, string? sourceName = null, string? sourceUuid = null) + public SceneItemCreatedPayload(string sceneName, string sceneUuid, string sourceName, string sourceUuid, int sceneItemId, int sceneItemIndex) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemEnableStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemEnableStateChanged.EventPayload.g.cs index 48a8e4e..99acf10 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemEnableStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemEnableStateChanged.EventPayload.g.cs @@ -43,14 +43,14 @@ public sealed partial record SceneItemEnableStateChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item is in /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -61,7 +61,7 @@ public SceneItemEnableStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemEnableStateChangedPayload(int sceneItemId, bool sceneItemEnabled, string? sceneName = null, string? sceneUuid = null) + public SceneItemEnableStateChangedPayload(string sceneName, string sceneUuid, int sceneItemId, bool sceneItemEnabled) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs index cffcdb6..51570c5 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record SceneItemListReindexedPayload /// [JsonPropertyName("sceneItems")] [Key("sceneItems")] - public System.Collections.Generic.List? SceneItems { get; init; } + public required System.Collections.Generic.List SceneItems { get; init; } /// /// Name of the scene /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public SceneItemListReindexedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneItemListReindexedPayload(string? sceneName = null, string? sceneUuid = null, System.Collections.Generic.List? sceneItems = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneItemListReindexedPayload(string sceneName, string sceneUuid, System.Collections.Generic.List sceneItems) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemLockStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemLockStateChanged.EventPayload.g.cs index 6ce7abd..b13fc77 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemLockStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemLockStateChanged.EventPayload.g.cs @@ -43,14 +43,14 @@ public sealed partial record SceneItemLockStateChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item is in /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -61,7 +61,7 @@ public SceneItemLockStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemLockStateChangedPayload(int sceneItemId, bool sceneItemLocked, string? sceneName = null, string? sceneUuid = null) + public SceneItemLockStateChangedPayload(string sceneName, string sceneUuid, int sceneItemId, bool sceneItemLocked) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemRemoved.EventPayload.g.cs index 4a4293e..5d69c8b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemRemoved.EventPayload.g.cs @@ -38,28 +38,28 @@ public sealed partial record SceneItemRemovedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item was removed from /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// /// Name of the underlying source (input/scene) /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// /// UUID of the underlying source (input/scene) /// [JsonPropertyName("sourceUuid")] [Key("sourceUuid")] - public string? SourceUuid { get; init; } + public required string SourceUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -70,7 +70,7 @@ public SceneItemRemovedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemRemovedPayload(int sceneItemId, string? sceneName = null, string? sceneUuid = null, string? sourceName = null, string? sourceUuid = null) + public SceneItemRemovedPayload(string sceneName, string sceneUuid, string sourceName, string sourceUuid, int sceneItemId) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemSelected.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemSelected.EventPayload.g.cs index 762d26b..defb464 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemSelected.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemSelected.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record SceneItemSelectedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item is in /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public SceneItemSelectedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemSelectedPayload(int sceneItemId, string? sceneName = null, string? sceneUuid = null) + public SceneItemSelectedPayload(string sceneName, string sceneUuid, int sceneItemId) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs index bd2c2e0..636a70d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs @@ -36,21 +36,21 @@ public sealed partial record SceneItemTransformChangedPayload /// [JsonPropertyName("sceneItemTransform")] [Key("sceneItemTransform")] - public ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? SceneItemTransform { get; init; } + public required ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub SceneItemTransform { get; init; } /// /// The name of the scene the item is in /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// The UUID of the scene the item is in /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -61,7 +61,7 @@ public SceneItemTransformChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemTransformChangedPayload(int sceneItemId, string? sceneName = null, string? sceneUuid = null, ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = null) + public SceneItemTransformChangedPayload(string sceneName, string sceneUuid, int sceneItemId, ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub sceneItemTransform) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs index 98aa9a3..e54479f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs @@ -31,7 +31,7 @@ public sealed partial record SceneListChangedPayload /// [JsonPropertyName("scenes")] [Key("scenes")] - public System.Collections.Generic.List? Scenes { get; init; } + public required System.Collections.Generic.List Scenes { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public SceneListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneListChangedPayload(System.Collections.Generic.List? scenes = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneListChangedPayload(System.Collections.Generic.List scenes) { this.Scenes = scenes; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneNameChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneNameChanged.EventPayload.g.cs index c664e25..0b08add 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneNameChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneNameChanged.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record SceneNameChangedPayload /// [JsonPropertyName("oldSceneName")] [Key("oldSceneName")] - public string? OldSceneName { get; init; } + public required string OldSceneName { get; init; } /// /// New name of the scene /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public SceneNameChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneNameChangedPayload(string? sceneUuid = null, string? oldSceneName = null, string? sceneName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneNameChangedPayload(string sceneUuid, string oldSceneName, string sceneName) { this.SceneUuid = sceneUuid; this.OldSceneName = oldSceneName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneRemoved.EventPayload.g.cs index 5e17f20..e40bb42 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneRemoved.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record SceneRemovedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the removed scene /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public SceneRemovedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneRemovedPayload(bool isGroup, string? sceneName = null, string? sceneUuid = null) + public SceneRemovedPayload(string sceneName, string sceneUuid, bool isGroup) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionEnded.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionEnded.EventPayload.g.cs index 43902d7..f57cf2d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionEnded.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionEnded.EventPayload.g.cs @@ -31,14 +31,14 @@ public sealed partial record SceneTransitionEndedPayload /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// Scene transition UUID /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -48,7 +48,8 @@ public SceneTransitionEndedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneTransitionEndedPayload(string? transitionName = null, string? transitionUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneTransitionEndedPayload(string transitionName, string transitionUuid) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionStarted.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionStarted.EventPayload.g.cs index 00d0c4e..8b4d0a0 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionStarted.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionStarted.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record SceneTransitionStartedPayload /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// Scene transition UUID /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public SceneTransitionStartedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneTransitionStartedPayload(string? transitionName = null, string? transitionUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneTransitionStartedPayload(string transitionName, string transitionUuid) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionVideoEnded.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionVideoEnded.EventPayload.g.cs index 2e65463..5930f1c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionVideoEnded.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionVideoEnded.EventPayload.g.cs @@ -34,14 +34,14 @@ public sealed partial record SceneTransitionVideoEndedPayload /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// Scene transition UUID /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -51,7 +51,8 @@ public SceneTransitionVideoEndedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneTransitionVideoEndedPayload(string? transitionName = null, string? transitionUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneTransitionVideoEndedPayload(string transitionName, string transitionUuid) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ScreenshotSaved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ScreenshotSaved.EventPayload.g.cs index 9c332a3..5bb48d2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ScreenshotSaved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ScreenshotSaved.EventPayload.g.cs @@ -33,7 +33,7 @@ public sealed partial record ScreenshotSavedPayload /// [JsonPropertyName("savedScreenshotPath")] [Key("savedScreenshotPath")] - public string? SavedScreenshotPath { get; init; } + public required string SavedScreenshotPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -43,7 +43,8 @@ public ScreenshotSavedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public ScreenshotSavedPayload(string? savedScreenshotPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public ScreenshotSavedPayload(string savedScreenshotPath) { this.SavedScreenshotPath = savedScreenshotPath; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs index 7632912..ad9b2b4 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs @@ -43,14 +43,14 @@ public sealed partial record SourceFilterCreatedPayload /// [JsonPropertyName("filterKind")] [Key("filterKind")] - public string? FilterKind { get; init; } + public required string FilterKind { get; init; } /// /// Name of the filter /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// The settings configured to the filter when it was created @@ -64,7 +64,7 @@ public sealed partial record SourceFilterCreatedPayload /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -75,7 +75,7 @@ public SourceFilterCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SourceFilterCreatedPayload(int filterIndex, string? sourceName = null, string? filterName = null, string? filterKind = null, System.Text.Json.JsonElement? filterSettings = null, System.Text.Json.JsonElement? defaultFilterSettings = null) + public SourceFilterCreatedPayload(string sourceName, string filterName, string filterKind, int filterIndex, System.Text.Json.JsonElement? filterSettings = default, System.Text.Json.JsonElement? defaultFilterSettings = default) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterEnableStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterEnableStateChanged.EventPayload.g.cs index d78a96e..984c893 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterEnableStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterEnableStateChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record SourceFilterEnableStateChangedPayload /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// Name of the source the filter is on /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public SourceFilterEnableStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SourceFilterEnableStateChangedPayload(bool filterEnabled, string? sourceName = null, string? filterName = null) + public SourceFilterEnableStateChangedPayload(string sourceName, string filterName, bool filterEnabled) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs index 7e2ce55..7c77152 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record SourceFilterListReindexedPayload /// [JsonPropertyName("filters")] [Key("filters")] - public System.Collections.Generic.List? Filters { get; init; } + public required System.Collections.Generic.List Filters { get; init; } /// /// Name of the source /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public SourceFilterListReindexedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterListReindexedPayload(string? sourceName = null, System.Collections.Generic.List? filters = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SourceFilterListReindexedPayload(string sourceName, System.Collections.Generic.List filters) { this.SourceName = sourceName; this.Filters = filters; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterNameChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterNameChanged.EventPayload.g.cs index d561018..3b19ce2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterNameChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterNameChanged.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record SourceFilterNameChangedPayload /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// Old name of the filter /// [JsonPropertyName("oldFilterName")] [Key("oldFilterName")] - public string? OldFilterName { get; init; } + public required string OldFilterName { get; init; } /// /// The source the filter is on /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public SourceFilterNameChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterNameChangedPayload(string? sourceName = null, string? oldFilterName = null, string? filterName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SourceFilterNameChangedPayload(string sourceName, string oldFilterName, string filterName) { this.SourceName = sourceName; this.OldFilterName = oldFilterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterRemoved.EventPayload.g.cs index a4a9aa1..c8c0d6f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterRemoved.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record SourceFilterRemovedPayload /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// Name of the source the filter was on /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public SourceFilterRemovedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterRemovedPayload(string? sourceName = null, string? filterName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SourceFilterRemovedPayload(string sourceName, string filterName) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs index 7d4bb98..005eb19 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record SourceFilterSettingsChangedPayload /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// New settings object of the filter @@ -43,7 +43,7 @@ public sealed partial record SourceFilterSettingsChangedPayload /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public SourceFilterSettingsChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterSettingsChangedPayload(string? sourceName = null, string? filterName = null, System.Text.Json.JsonElement? filterSettings = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SourceFilterSettingsChangedPayload(string sourceName, string filterName, System.Text.Json.JsonElement? filterSettings = default) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/StreamStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/StreamStateChanged.EventPayload.g.cs index 6200c07..61f570f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/StreamStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/StreamStateChanged.EventPayload.g.cs @@ -36,7 +36,9 @@ public sealed partial record StreamStateChangedPayload /// [JsonPropertyName("outputState")] [Key("outputState")] - public string? OutputState { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.OutputState OutputState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -47,7 +49,7 @@ public StreamStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public StreamStateChangedPayload(bool outputActive, string? outputState = null) + public StreamStateChangedPayload(bool outputActive, ObsWebSocket.Core.Protocol.Generated.OutputState outputState = default) { this.OutputActive = outputActive; this.OutputState = outputState; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs index 5ca8346..fa22054 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs @@ -39,14 +39,14 @@ public sealed partial record VendorEventPayload /// [JsonPropertyName("eventType")] [Key("eventType")] - public string? EventType { get; init; } + public required string EventType { get; init; } /// /// Name of the vendor emitting the event /// [JsonPropertyName("vendorName")] [Key("vendorName")] - public string? VendorName { get; init; } + public required string VendorName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -56,7 +56,8 @@ public VendorEventPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public VendorEventPayload(string? vendorName = null, string? eventType = null, System.Text.Json.JsonElement? eventData = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public VendorEventPayload(string vendorName, string eventType, System.Text.Json.JsonElement? eventData = default) { this.VendorName = vendorName; this.EventType = eventType; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/VirtualcamStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/VirtualcamStateChanged.EventPayload.g.cs index b92b4f9..34b4ac7 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/VirtualcamStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/VirtualcamStateChanged.EventPayload.g.cs @@ -36,7 +36,9 @@ public sealed partial record VirtualcamStateChangedPayload /// [JsonPropertyName("outputState")] [Key("outputState")] - public string? OutputState { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.OutputState OutputState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -47,7 +49,7 @@ public VirtualcamStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public VirtualcamStateChangedPayload(bool outputActive, string? outputState = null) + public VirtualcamStateChangedPayload(bool outputActive, ObsWebSocket.Core.Protocol.Generated.OutputState outputState = default) { this.OutputActive = outputActive; this.OutputState = outputState; diff --git a/ObsWebSocket.Core/Generated/Protocol/Generated/MediaInputAction.TypedEnum.g.cs b/ObsWebSocket.Core/Generated/Protocol/Generated/MediaInputAction.TypedEnum.g.cs index 7ca6202..f4a01e0 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Generated/MediaInputAction.TypedEnum.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Generated/MediaInputAction.TypedEnum.g.cs @@ -74,3 +74,31 @@ public static class MediaInputActionExtensions _ => null, }; } + +/// +/// Reads and writes as the protocol string in JSON. +/// +public sealed class MediaInputActionJsonConverter : System.Text.Json.Serialization.JsonConverter +{ + /// + public override MediaInputAction Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) => + MediaInputActionExtensions.FromWireValue(reader.GetString()) ?? MediaInputAction.None; + + /// + public override void Write(System.Text.Json.Utf8JsonWriter writer, MediaInputAction value, System.Text.Json.JsonSerializerOptions options) => + writer.WriteStringValue(value.ToWireValue()); +} + +/// +/// Reads and writes as the protocol string in MessagePack. +/// +public sealed class MediaInputActionMessagePackFormatter : MessagePack.Formatters.IMessagePackFormatter +{ + /// + public MediaInputAction Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) => + MediaInputActionExtensions.FromWireValue(reader.ReadString()) ?? MediaInputAction.None; + + /// + public void Serialize(ref MessagePack.MessagePackWriter writer, MediaInputAction value, MessagePack.MessagePackSerializerOptions options) => + writer.Write(value.ToWireValue()); +} diff --git a/ObsWebSocket.Core/Generated/Protocol/Generated/OutputState.TypedEnum.g.cs b/ObsWebSocket.Core/Generated/Protocol/Generated/OutputState.TypedEnum.g.cs index 1bbac8c..1cef17a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Generated/OutputState.TypedEnum.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Generated/OutputState.TypedEnum.g.cs @@ -86,3 +86,31 @@ public static class OutputStateExtensions _ => null, }; } + +/// +/// Reads and writes as the protocol string in JSON. +/// +public sealed class OutputStateJsonConverter : System.Text.Json.Serialization.JsonConverter +{ + /// + public override OutputState Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) => + OutputStateExtensions.FromWireValue(reader.GetString()) ?? OutputState.Unknown; + + /// + public override void Write(System.Text.Json.Utf8JsonWriter writer, OutputState value, System.Text.Json.JsonSerializerOptions options) => + writer.WriteStringValue(value.ToWireValue()); +} + +/// +/// Reads and writes as the protocol string in MessagePack. +/// +public sealed class OutputStateMessagePackFormatter : MessagePack.Formatters.IMessagePackFormatter +{ + /// + public OutputState Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) => + OutputStateExtensions.FromWireValue(reader.ReadString()) ?? OutputState.Unknown; + + /// + public void Serialize(ref MessagePack.MessagePackWriter writer, OutputState value, MessagePack.MessagePackSerializerOptions options) => + writer.Write(value.ToWireValue()); +} diff --git a/ObsWebSocket.Core/Generated/Protocol/Requests/CallVendorRequest.Request.g.cs b/ObsWebSocket.Core/Generated/Protocol/Requests/CallVendorRequest.Request.g.cs index b7ec4f8..8b1cdb5 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Requests/CallVendorRequest.Request.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Requests/CallVendorRequest.Request.g.cs @@ -67,7 +67,7 @@ public CallVendorRequestRequestData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public CallVendorRequestRequestData(string vendorName, string requestType, System.Text.Json.JsonElement? requestData = null) + public CallVendorRequestRequestData(string vendorName, string requestType, System.Text.Json.JsonElement? requestData = default) { this.VendorName = vendorName; this.RequestType = requestType; diff --git a/ObsWebSocket.Core/Generated/Protocol/Requests/CreateInput.Request.g.cs b/ObsWebSocket.Core/Generated/Protocol/Requests/CreateInput.Request.g.cs index aff47e8..f22e769 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Requests/CreateInput.Request.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Requests/CreateInput.Request.g.cs @@ -108,7 +108,7 @@ public CreateInputRequestData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public CreateInputRequestData(string inputName, string inputKind, string? canvasUuid = null, string? sceneName = null, string? sceneUuid = null, System.Text.Json.JsonElement? inputSettings = null, bool? sceneItemEnabled = null) + public CreateInputRequestData(string inputName, string inputKind, string? canvasUuid = null, string? sceneName = null, string? sceneUuid = null, System.Text.Json.JsonElement? inputSettings = default, bool? sceneItemEnabled = null) { this.CanvasUuid = canvasUuid; this.SceneName = sceneName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Requests/CreateSourceFilter.Request.g.cs b/ObsWebSocket.Core/Generated/Protocol/Requests/CreateSourceFilter.Request.g.cs index c6e1632..578649f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Requests/CreateSourceFilter.Request.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Requests/CreateSourceFilter.Request.g.cs @@ -97,7 +97,7 @@ public CreateSourceFilterRequestData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public CreateSourceFilterRequestData(string filterName, string filterKind, string? canvasUuid = null, string? sourceName = null, string? sourceUuid = null, System.Text.Json.JsonElement? filterSettings = null) + public CreateSourceFilterRequestData(string filterName, string filterKind, string? canvasUuid = null, string? sourceName = null, string? sourceUuid = null, System.Text.Json.JsonElement? filterSettings = default) { this.CanvasUuid = canvasUuid; this.SourceName = sourceName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Requests/TriggerMediaInputAction.Request.g.cs b/ObsWebSocket.Core/Generated/Protocol/Requests/TriggerMediaInputAction.Request.g.cs index b8f067a..9751408 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Requests/TriggerMediaInputAction.Request.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Requests/TriggerMediaInputAction.Request.g.cs @@ -54,7 +54,9 @@ public sealed partial record TriggerMediaInputActionRequestData /// [JsonPropertyName("mediaAction")] [Key("mediaAction")] - public required string MediaAction { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.MediaInputActionJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.MediaInputActionMessagePackFormatter))] + public required ObsWebSocket.Core.Protocol.Generated.MediaInputAction MediaAction { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -65,7 +67,7 @@ public TriggerMediaInputActionRequestData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public TriggerMediaInputActionRequestData(string mediaAction, string? inputName = null, string? inputUuid = null) + public TriggerMediaInputActionRequestData(ObsWebSocket.Core.Protocol.Generated.MediaInputAction mediaAction, string? inputName = null, string? inputUuid = null) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs index 4db8e0a..27be420 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs @@ -32,7 +32,7 @@ public sealed partial record CallVendorRequestResponseData /// [JsonPropertyName("requestType")] [Key("requestType")] - public string? RequestType { get; init; } + public required string RequestType { get; init; } /// /// Object containing appropriate response data. {} if request does not provide any response data @@ -46,7 +46,7 @@ public sealed partial record CallVendorRequestResponseData /// [JsonPropertyName("vendorName")] [Key("vendorName")] - public string? VendorName { get; init; } + public required string VendorName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -56,7 +56,8 @@ public CallVendorRequestResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CallVendorRequestResponseData(string? vendorName = null, string? requestType = null, System.Text.Json.JsonElement? responseData = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CallVendorRequestResponseData(string vendorName, string requestType, System.Text.Json.JsonElement? responseData = default) { this.VendorName = vendorName; this.RequestType = requestType; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/CreateInput.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/CreateInput.Response.g.cs index c032cd3..feaba6e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/CreateInput.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/CreateInput.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record CreateInputResponseData /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// ID of the newly created scene item @@ -47,7 +47,7 @@ public CreateInputResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public CreateInputResponseData(int sceneItemId, string? inputUuid = null) + public CreateInputResponseData(string inputUuid, int sceneItemId) { this.InputUuid = inputUuid; this.SceneItemId = sceneItemId; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/CreateScene.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/CreateScene.Response.g.cs index 03c84ce..dc5a303 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/CreateScene.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/CreateScene.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record CreateSceneResponseData /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public CreateSceneResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CreateSceneResponseData(string? sceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CreateSceneResponseData(string sceneUuid) { this.SceneUuid = sceneUuid; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs index 90b57fe..4c86d8c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetCanvasListResponseData /// [JsonPropertyName("canvases")] [Key("canvases")] - public System.Collections.Generic.List? Canvases { get; init; } + public required System.Collections.Generic.List Canvases { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetCanvasListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetCanvasListResponseData(System.Collections.Generic.List? canvases = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetCanvasListResponseData(System.Collections.Generic.List canvases) { this.Canvases = canvases; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentPreviewScene.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentPreviewScene.Response.g.cs index 11154ae..b5d17f7 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentPreviewScene.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentPreviewScene.Response.g.cs @@ -33,28 +33,28 @@ public sealed partial record GetCurrentPreviewSceneResponseData /// [JsonPropertyName("currentPreviewSceneName")] [Key("currentPreviewSceneName")] - public string? CurrentPreviewSceneName { get; init; } + public required string CurrentPreviewSceneName { get; init; } /// /// Current preview scene UUID /// [JsonPropertyName("currentPreviewSceneUuid")] [Key("currentPreviewSceneUuid")] - public string? CurrentPreviewSceneUuid { get; init; } + public required string CurrentPreviewSceneUuid { get; init; } /// /// Current preview scene name /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// Current preview scene UUID /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -64,7 +64,8 @@ public GetCurrentPreviewSceneResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetCurrentPreviewSceneResponseData(string? sceneName = null, string? sceneUuid = null, string? currentPreviewSceneName = null, string? currentPreviewSceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetCurrentPreviewSceneResponseData(string sceneName, string sceneUuid, string currentPreviewSceneName, string currentPreviewSceneUuid) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentProgramScene.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentProgramScene.Response.g.cs index 8702319..85c179d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentProgramScene.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentProgramScene.Response.g.cs @@ -33,28 +33,28 @@ public sealed partial record GetCurrentProgramSceneResponseData /// [JsonPropertyName("currentProgramSceneName")] [Key("currentProgramSceneName")] - public string? CurrentProgramSceneName { get; init; } + public required string CurrentProgramSceneName { get; init; } /// /// Current program scene UUID (Deprecated) /// [JsonPropertyName("currentProgramSceneUuid")] [Key("currentProgramSceneUuid")] - public string? CurrentProgramSceneUuid { get; init; } + public required string CurrentProgramSceneUuid { get; init; } /// /// Current program scene name /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// Current program scene UUID /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -64,7 +64,8 @@ public GetCurrentProgramSceneResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetCurrentProgramSceneResponseData(string? sceneName = null, string? sceneUuid = null, string? currentProgramSceneName = null, string? currentProgramSceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetCurrentProgramSceneResponseData(string sceneName, string sceneUuid, string currentProgramSceneName, string currentProgramSceneUuid) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs index 54f270a..2e1ebc2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs @@ -50,14 +50,14 @@ public sealed partial record GetCurrentSceneTransitionResponseData /// [JsonPropertyName("transitionKind")] [Key("transitionKind")] - public string? TransitionKind { get; init; } + public required string TransitionKind { get; init; } /// /// Name of the transition /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// Object of settings for the transition. `null` if transition is not configurable @@ -71,7 +71,7 @@ public sealed partial record GetCurrentSceneTransitionResponseData /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -82,7 +82,7 @@ public GetCurrentSceneTransitionResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetCurrentSceneTransitionResponseData(bool transitionFixed, bool transitionConfigurable, string? transitionName = null, string? transitionUuid = null, string? transitionKind = null, int? transitionDuration = null, System.Text.Json.JsonElement? transitionSettings = null) + public GetCurrentSceneTransitionResponseData(string transitionName, string transitionUuid, string transitionKind, bool transitionFixed, bool transitionConfigurable, int? transitionDuration = null, System.Text.Json.JsonElement? transitionSettings = default) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs index 7a59dd6..6320ecc 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetGroupListResponseData /// [JsonPropertyName("groups")] [Key("groups")] - public System.Collections.Generic.List? Groups { get; init; } + public required System.Collections.Generic.List Groups { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetGroupListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetGroupListResponseData(System.Collections.Generic.List? groups = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetGroupListResponseData(System.Collections.Generic.List groups) { this.Groups = groups; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs index faa6d2a..7a8e22e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs @@ -33,7 +33,7 @@ public sealed partial record GetGroupSceneItemListResponseData /// [JsonPropertyName("sceneItems")] [Key("sceneItems")] - public System.Collections.Generic.List? SceneItems { get; init; } + public required System.Collections.Generic.List SceneItems { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -43,7 +43,8 @@ public GetGroupSceneItemListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetGroupSceneItemListResponseData(System.Collections.Generic.List? sceneItems = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetGroupSceneItemListResponseData(System.Collections.Generic.List sceneItems) { this.SceneItems = sceneItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs index 51b51f8..8341aaf 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetHotkeyListResponseData /// [JsonPropertyName("hotkeys")] [Key("hotkeys")] - public System.Collections.Generic.List? Hotkeys { get; init; } + public required System.Collections.Generic.List Hotkeys { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetHotkeyListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetHotkeyListResponseData(System.Collections.Generic.List? hotkeys = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetHotkeyListResponseData(System.Collections.Generic.List hotkeys) { this.Hotkeys = hotkeys; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioMonitorType.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioMonitorType.Response.g.cs index 530b1fa..8229b39 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioMonitorType.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioMonitorType.Response.g.cs @@ -35,7 +35,7 @@ public sealed partial record GetInputAudioMonitorTypeResponseData /// [JsonPropertyName("monitorType")] [Key("monitorType")] - public string? MonitorType { get; init; } + public required string MonitorType { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -45,7 +45,8 @@ public GetInputAudioMonitorTypeResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputAudioMonitorTypeResponseData(string? monitorType = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputAudioMonitorTypeResponseData(string monitorType) { this.MonitorType = monitorType; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioTracks.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioTracks.Response.g.cs index 3de8e33..9e730b6 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioTracks.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioTracks.Response.g.cs @@ -39,7 +39,7 @@ public GetInputAudioTracksResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputAudioTracksResponseData(System.Collections.Generic.Dictionary? inputAudioTracks = null) + public GetInputAudioTracksResponseData(System.Collections.Generic.Dictionary? inputAudioTracks = default) { this.InputAudioTracks = inputAudioTracks; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDefaultSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDefaultSettings.Response.g.cs index 3cae6e2..a6b73fa 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDefaultSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDefaultSettings.Response.g.cs @@ -39,7 +39,7 @@ public GetInputDefaultSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputDefaultSettingsResponseData(System.Text.Json.JsonElement? defaultInputSettings = null) + public GetInputDefaultSettingsResponseData(System.Text.Json.JsonElement? defaultInputSettings = default) { this.DefaultInputSettings = defaultInputSettings; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceFieldOrder.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceFieldOrder.Response.g.cs index f8d3540..2b8a903 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceFieldOrder.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceFieldOrder.Response.g.cs @@ -36,7 +36,7 @@ public sealed partial record GetInputDeinterlaceFieldOrderResponseData /// [JsonPropertyName("inputDeinterlaceFieldOrder")] [Key("inputDeinterlaceFieldOrder")] - public string? InputDeinterlaceFieldOrder { get; init; } + public required string InputDeinterlaceFieldOrder { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetInputDeinterlaceFieldOrderResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputDeinterlaceFieldOrderResponseData(string? inputDeinterlaceFieldOrder = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputDeinterlaceFieldOrderResponseData(string inputDeinterlaceFieldOrder) { this.InputDeinterlaceFieldOrder = inputDeinterlaceFieldOrder; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceMode.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceMode.Response.g.cs index 81cc25a..05fbbe8 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceMode.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceMode.Response.g.cs @@ -43,7 +43,7 @@ public sealed partial record GetInputDeinterlaceModeResponseData /// [JsonPropertyName("inputDeinterlaceMode")] [Key("inputDeinterlaceMode")] - public string? InputDeinterlaceMode { get; init; } + public required string InputDeinterlaceMode { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public GetInputDeinterlaceModeResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputDeinterlaceModeResponseData(string? inputDeinterlaceMode = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputDeinterlaceModeResponseData(string inputDeinterlaceMode) { this.InputDeinterlaceMode = inputDeinterlaceMode; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs index d926cd5..6d26527 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetInputKindListResponseData /// [JsonPropertyName("inputKinds")] [Key("inputKinds")] - public System.Collections.Generic.List? InputKinds { get; init; } + public required System.Collections.Generic.List InputKinds { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetInputKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputKindListResponseData(System.Collections.Generic.List? inputKinds = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputKindListResponseData(System.Collections.Generic.List inputKinds) { this.InputKinds = inputKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs index fe8a6f7..ea28a4b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetInputListResponseData /// [JsonPropertyName("inputs")] [Key("inputs")] - public System.Collections.Generic.List? Inputs { get; init; } + public required System.Collections.Generic.List Inputs { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetInputListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputListResponseData(System.Collections.Generic.List? inputs = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputListResponseData(System.Collections.Generic.List inputs) { this.Inputs = inputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs index 22f90f1..4998a08 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetInputPropertiesListPropertyItemsResponseData /// [JsonPropertyName("propertyItems")] [Key("propertyItems")] - public System.Collections.Generic.List? PropertyItems { get; init; } + public required System.Collections.Generic.List PropertyItems { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetInputPropertiesListPropertyItemsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputPropertiesListPropertyItemsResponseData(System.Collections.Generic.List? propertyItems = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputPropertiesListPropertyItemsResponseData(System.Collections.Generic.List propertyItems) { this.PropertyItems = propertyItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs index bf2cd85..3d5a27c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetInputSettingsResponseData /// [JsonPropertyName("inputKind")] [Key("inputKind")] - public string? InputKind { get; init; } + public required string InputKind { get; init; } /// /// Object of settings for the input @@ -48,7 +48,8 @@ public GetInputSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputSettingsResponseData(System.Text.Json.JsonElement? inputSettings = null, string? inputKind = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputSettingsResponseData(string inputKind, System.Text.Json.JsonElement? inputSettings = default) { this.InputSettings = inputSettings; this.InputKind = inputKind; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetLastReplayBufferReplay.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetLastReplayBufferReplay.Response.g.cs index 303f27a..49ee625 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetLastReplayBufferReplay.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetLastReplayBufferReplay.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetLastReplayBufferReplayResponseData /// [JsonPropertyName("savedReplayPath")] [Key("savedReplayPath")] - public string? SavedReplayPath { get; init; } + public required string SavedReplayPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetLastReplayBufferReplayResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetLastReplayBufferReplayResponseData(string? savedReplayPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetLastReplayBufferReplayResponseData(string savedReplayPath) { this.SavedReplayPath = savedReplayPath; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMediaInputStatus.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMediaInputStatus.Response.g.cs index 54d974d..5555c1d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMediaInputStatus.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMediaInputStatus.Response.g.cs @@ -54,7 +54,7 @@ public sealed partial record GetMediaInputStatusResponseData /// [JsonPropertyName("mediaState")] [Key("mediaState")] - public string? MediaState { get; init; } + public required string MediaState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -64,7 +64,8 @@ public GetMediaInputStatusResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetMediaInputStatusResponseData(string? mediaState = null, long? mediaDuration = null, long? mediaCursor = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetMediaInputStatusResponseData(string mediaState, long? mediaDuration = null, long? mediaCursor = null) { this.MediaState = mediaState; this.MediaDuration = mediaDuration; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs index c7bf0ae..cfe1115 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetMonitorListResponseData /// [JsonPropertyName("monitors")] [Key("monitors")] - public System.Collections.Generic.List? Monitors { get; init; } + public required System.Collections.Generic.List Monitors { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetMonitorListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetMonitorListResponseData(System.Collections.Generic.List? monitors = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetMonitorListResponseData(System.Collections.Generic.List monitors) { this.Monitors = monitors; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs index f09911e..3c4f07a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetOutputListResponseData /// [JsonPropertyName("outputs")] [Key("outputs")] - public System.Collections.Generic.List? Outputs { get; init; } + public required System.Collections.Generic.List Outputs { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetOutputListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetOutputListResponseData(System.Collections.Generic.List? outputs = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetOutputListResponseData(System.Collections.Generic.List outputs) { this.Outputs = outputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputSettings.Response.g.cs index 6f7aff4..ad059ff 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputSettings.Response.g.cs @@ -39,7 +39,7 @@ public GetOutputSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetOutputSettingsResponseData(System.Text.Json.JsonElement? outputSettings = null) + public GetOutputSettingsResponseData(System.Text.Json.JsonElement? outputSettings = default) { this.OutputSettings = outputSettings; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputStatus.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputStatus.Response.g.cs index 9b0b776..bb89ae2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputStatus.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputStatus.Response.g.cs @@ -71,7 +71,7 @@ public sealed partial record GetOutputStatusResponseData /// [JsonPropertyName("outputTimecode")] [Key("outputTimecode")] - public string? OutputTimecode { get; init; } + public required string OutputTimecode { get; init; } /// /// Total number of frames delivered by the output's process @@ -89,7 +89,7 @@ public GetOutputStatusResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetOutputStatusResponseData(bool outputActive, bool outputReconnecting, long outputDuration, double outputCongestion, long outputBytes, int outputSkippedFrames, int outputTotalFrames, string? outputTimecode = null) + public GetOutputStatusResponseData(bool outputActive, bool outputReconnecting, string outputTimecode, long outputDuration, double outputCongestion, long outputBytes, int outputSkippedFrames, int outputTotalFrames) { this.OutputActive = outputActive; this.OutputReconnecting = outputReconnecting; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetPersistentData.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetPersistentData.Response.g.cs index 123f822..3372fd2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetPersistentData.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetPersistentData.Response.g.cs @@ -39,7 +39,7 @@ public GetPersistentDataResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetPersistentDataResponseData(System.Text.Json.JsonElement? slotValue = null) + public GetPersistentDataResponseData(System.Text.Json.JsonElement? slotValue = default) { this.SlotValue = slotValue; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs index cbf1325..8c6a5db 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs @@ -29,14 +29,14 @@ public sealed partial record GetProfileListResponseData /// [JsonPropertyName("currentProfileName")] [Key("currentProfileName")] - public string? CurrentProfileName { get; init; } + public required string CurrentProfileName { get; init; } /// /// Array of all available profiles /// [JsonPropertyName("profiles")] [Key("profiles")] - public System.Collections.Generic.List? Profiles { get; init; } + public required System.Collections.Generic.List Profiles { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetProfileListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetProfileListResponseData(string? currentProfileName = null, System.Collections.Generic.List? profiles = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetProfileListResponseData(string currentProfileName, System.Collections.Generic.List profiles) { this.CurrentProfileName = currentProfileName; this.Profiles = profiles; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordDirectory.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordDirectory.Response.g.cs index 7a0a39f..28cb753 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordDirectory.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordDirectory.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetRecordDirectoryResponseData /// [JsonPropertyName("recordDirectory")] [Key("recordDirectory")] - public string? RecordDirectory { get; init; } + public required string RecordDirectory { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetRecordDirectoryResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetRecordDirectoryResponseData(string? recordDirectory = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetRecordDirectoryResponseData(string recordDirectory) { this.RecordDirectory = recordDirectory; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordStatus.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordStatus.Response.g.cs index 5dbe7bc..7f0499f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordStatus.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordStatus.Response.g.cs @@ -57,7 +57,7 @@ public sealed partial record GetRecordStatusResponseData /// [JsonPropertyName("outputTimecode")] [Key("outputTimecode")] - public string? OutputTimecode { get; init; } + public required string OutputTimecode { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -68,7 +68,7 @@ public GetRecordStatusResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetRecordStatusResponseData(bool outputActive, bool outputPaused, long outputDuration, long outputBytes, string? outputTimecode = null) + public GetRecordStatusResponseData(bool outputActive, bool outputPaused, string outputTimecode, long outputDuration, long outputBytes) { this.OutputActive = outputActive; this.OutputPaused = outputPaused; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs index 43feb17..be07285 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs @@ -29,14 +29,14 @@ public sealed partial record GetSceneCollectionListResponseData /// [JsonPropertyName("currentSceneCollectionName")] [Key("currentSceneCollectionName")] - public string? CurrentSceneCollectionName { get; init; } + public required string CurrentSceneCollectionName { get; init; } /// /// Array of all available scene collections /// [JsonPropertyName("sceneCollections")] [Key("sceneCollections")] - public System.Collections.Generic.List? SceneCollections { get; init; } + public required System.Collections.Generic.List SceneCollections { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetSceneCollectionListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneCollectionListResponseData(string? currentSceneCollectionName = null, System.Collections.Generic.List? sceneCollections = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneCollectionListResponseData(string currentSceneCollectionName, System.Collections.Generic.List sceneCollections) { this.CurrentSceneCollectionName = currentSceneCollectionName; this.SceneCollections = sceneCollections; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemBlendMode.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemBlendMode.Response.g.cs index 9b835d9..678af56 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemBlendMode.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemBlendMode.Response.g.cs @@ -41,7 +41,7 @@ public sealed partial record GetSceneItemBlendModeResponseData /// [JsonPropertyName("sceneItemBlendMode")] [Key("sceneItemBlendMode")] - public string? SceneItemBlendMode { get; init; } + public required string SceneItemBlendMode { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -51,7 +51,8 @@ public GetSceneItemBlendModeResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemBlendModeResponseData(string? sceneItemBlendMode = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneItemBlendModeResponseData(string sceneItemBlendMode) { this.SceneItemBlendMode = sceneItemBlendMode; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs index 6e5f373..00ecc2a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetSceneItemListResponseData /// [JsonPropertyName("sceneItems")] [Key("sceneItems")] - public System.Collections.Generic.List? SceneItems { get; init; } + public required System.Collections.Generic.List SceneItems { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetSceneItemListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemListResponseData(System.Collections.Generic.List? sceneItems = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneItemListResponseData(System.Collections.Generic.List sceneItems) { this.SceneItems = sceneItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemSource.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemSource.Response.g.cs index 16eb829..16616b1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemSource.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemSource.Response.g.cs @@ -29,14 +29,14 @@ public sealed partial record GetSceneItemSourceResponseData /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// /// UUID of the source associated with the scene item /// [JsonPropertyName("sourceUuid")] [Key("sourceUuid")] - public string? SourceUuid { get; init; } + public required string SourceUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetSceneItemSourceResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemSourceResponseData(string? sourceName = null, string? sourceUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneItemSourceResponseData(string sourceName, string sourceUuid) { this.SourceName = sourceName; this.SourceUuid = sourceUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs index 3a58313..dad352d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetSceneItemTransformResponseData /// [JsonPropertyName("sceneItemTransform")] [Key("sceneItemTransform")] - public ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? SceneItemTransform { get; init; } + public required ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub SceneItemTransform { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetSceneItemTransformResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemTransformResponseData(ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneItemTransformResponseData(ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub sceneItemTransform) { this.SceneItemTransform = sceneItemTransform; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs index 8088553..a293190 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs @@ -57,7 +57,7 @@ public sealed partial record GetSceneListResponseData /// [JsonPropertyName("scenes")] [Key("scenes")] - public System.Collections.Generic.List? Scenes { get; init; } + public required System.Collections.Generic.List Scenes { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -67,7 +67,8 @@ public GetSceneListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneListResponseData(string? currentProgramSceneName = null, string? currentProgramSceneUuid = null, string? currentPreviewSceneName = null, string? currentPreviewSceneUuid = null, System.Collections.Generic.List? scenes = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneListResponseData(System.Collections.Generic.List scenes, string? currentProgramSceneName = null, string? currentProgramSceneUuid = null, string? currentPreviewSceneName = null, string? currentPreviewSceneUuid = null) { this.CurrentProgramSceneName = currentProgramSceneName; this.CurrentProgramSceneUuid = currentProgramSceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs index ea28080..18a4fe1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs @@ -50,7 +50,7 @@ public sealed partial record GetSceneTransitionListResponseData /// [JsonPropertyName("transitions")] [Key("transitions")] - public System.Collections.Generic.List? Transitions { get; init; } + public required System.Collections.Generic.List Transitions { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -60,7 +60,8 @@ public GetSceneTransitionListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneTransitionListResponseData(string? currentSceneTransitionName = null, string? currentSceneTransitionUuid = null, string? currentSceneTransitionKind = null, System.Collections.Generic.List? transitions = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneTransitionListResponseData(System.Collections.Generic.List transitions, string? currentSceneTransitionName = null, string? currentSceneTransitionUuid = null, string? currentSceneTransitionKind = null) { this.CurrentSceneTransitionName = currentSceneTransitionName; this.CurrentSceneTransitionUuid = currentSceneTransitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs index ef6669c..a965564 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs @@ -43,7 +43,7 @@ public sealed partial record GetSourceFilterResponseData /// [JsonPropertyName("filterKind")] [Key("filterKind")] - public string? FilterKind { get; init; } + public required string FilterKind { get; init; } /// /// Settings object associated with the filter @@ -61,7 +61,7 @@ public GetSourceFilterResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetSourceFilterResponseData(bool filterEnabled, int filterIndex, string? filterKind = null, System.Text.Json.JsonElement? filterSettings = null) + public GetSourceFilterResponseData(bool filterEnabled, int filterIndex, string filterKind, System.Text.Json.JsonElement? filterSettings = default) { this.FilterEnabled = filterEnabled; this.FilterIndex = filterIndex; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterDefaultSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterDefaultSettings.Response.g.cs index baec898..3a8582f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterDefaultSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterDefaultSettings.Response.g.cs @@ -39,7 +39,7 @@ public GetSourceFilterDefaultSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceFilterDefaultSettingsResponseData(System.Text.Json.JsonElement? defaultFilterSettings = null) + public GetSourceFilterDefaultSettingsResponseData(System.Text.Json.JsonElement? defaultFilterSettings = default) { this.DefaultFilterSettings = defaultFilterSettings; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs index 7e3c343..be71509 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetSourceFilterKindListResponseData /// [JsonPropertyName("sourceFilterKinds")] [Key("sourceFilterKinds")] - public System.Collections.Generic.List? SourceFilterKinds { get; init; } + public required System.Collections.Generic.List SourceFilterKinds { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetSourceFilterKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceFilterKindListResponseData(System.Collections.Generic.List? sourceFilterKinds = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSourceFilterKindListResponseData(System.Collections.Generic.List sourceFilterKinds) { this.SourceFilterKinds = sourceFilterKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs index c45922f..8e48091 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetSourceFilterListResponseData /// [JsonPropertyName("filters")] [Key("filters")] - public System.Collections.Generic.List? Filters { get; init; } + public required System.Collections.Generic.List Filters { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetSourceFilterListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceFilterListResponseData(System.Collections.Generic.List? filters = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSourceFilterListResponseData(System.Collections.Generic.List filters) { this.Filters = filters; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceScreenshot.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceScreenshot.Response.g.cs index f055243..7374ff4 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceScreenshot.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceScreenshot.Response.g.cs @@ -34,7 +34,7 @@ public sealed partial record GetSourceScreenshotResponseData /// [JsonPropertyName("imageData")] [Key("imageData")] - public string? ImageData { get; init; } + public required string ImageData { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -44,7 +44,8 @@ public GetSourceScreenshotResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceScreenshotResponseData(string? imageData = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSourceScreenshotResponseData(string imageData) { this.ImageData = imageData; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSpecialInputs.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSpecialInputs.Response.g.cs index 714b455..4181309 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSpecialInputs.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSpecialInputs.Response.g.cs @@ -29,42 +29,42 @@ public sealed partial record GetSpecialInputsResponseData /// [JsonPropertyName("desktop1")] [Key("desktop1")] - public string? Desktop1 { get; init; } + public required string Desktop1 { get; init; } /// /// Name of the Desktop Audio 2 input /// [JsonPropertyName("desktop2")] [Key("desktop2")] - public string? Desktop2 { get; init; } + public required string Desktop2 { get; init; } /// /// Name of the Mic/Auxiliary Audio input /// [JsonPropertyName("mic1")] [Key("mic1")] - public string? Mic1 { get; init; } + public required string Mic1 { get; init; } /// /// Name of the Mic/Auxiliary Audio 2 input /// [JsonPropertyName("mic2")] [Key("mic2")] - public string? Mic2 { get; init; } + public required string Mic2 { get; init; } /// /// Name of the Mic/Auxiliary Audio 3 input /// [JsonPropertyName("mic3")] [Key("mic3")] - public string? Mic3 { get; init; } + public required string Mic3 { get; init; } /// /// Name of the Mic/Auxiliary Audio 4 input /// [JsonPropertyName("mic4")] [Key("mic4")] - public string? Mic4 { get; init; } + public required string Mic4 { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -74,7 +74,8 @@ public GetSpecialInputsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSpecialInputsResponseData(string? desktop1 = null, string? desktop2 = null, string? mic1 = null, string? mic2 = null, string? mic3 = null, string? mic4 = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSpecialInputsResponseData(string desktop1, string desktop2, string mic1, string mic2, string mic3, string mic4) { this.Desktop1 = desktop1; this.Desktop2 = desktop2; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs index 387481f..4a01c1d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs @@ -36,7 +36,7 @@ public sealed partial record GetStreamServiceSettingsResponseData /// [JsonPropertyName("streamServiceType")] [Key("streamServiceType")] - public string? StreamServiceType { get; init; } + public required string StreamServiceType { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetStreamServiceSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetStreamServiceSettingsResponseData(string? streamServiceType = null, System.Text.Json.JsonElement? streamServiceSettings = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetStreamServiceSettingsResponseData(string streamServiceType, System.Text.Json.JsonElement? streamServiceSettings = default) { this.StreamServiceType = streamServiceType; this.StreamServiceSettings = streamServiceSettings; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamStatus.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamStatus.Response.g.cs index c8119b0..47912f4 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamStatus.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamStatus.Response.g.cs @@ -71,7 +71,7 @@ public sealed partial record GetStreamStatusResponseData /// [JsonPropertyName("outputTimecode")] [Key("outputTimecode")] - public string? OutputTimecode { get; init; } + public required string OutputTimecode { get; init; } /// /// Total number of frames delivered by the output's process @@ -89,7 +89,7 @@ public GetStreamStatusResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetStreamStatusResponseData(bool outputActive, bool outputReconnecting, long outputDuration, double outputCongestion, long outputBytes, int outputSkippedFrames, int outputTotalFrames, string? outputTimecode = null) + public GetStreamStatusResponseData(bool outputActive, bool outputReconnecting, string outputTimecode, long outputDuration, double outputCongestion, long outputBytes, int outputSkippedFrames, int outputTotalFrames) { this.OutputActive = outputActive; this.OutputReconnecting = outputReconnecting; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs index e68225d..0bc3ca9 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetTransitionKindListResponseData /// [JsonPropertyName("transitionKinds")] [Key("transitionKinds")] - public System.Collections.Generic.List? TransitionKinds { get; init; } + public required System.Collections.Generic.List TransitionKinds { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetTransitionKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetTransitionKindListResponseData(System.Collections.Generic.List? transitionKinds = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetTransitionKindListResponseData(System.Collections.Generic.List transitionKinds) { this.TransitionKinds = transitionKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs index 56eb552..3bdc83c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs @@ -29,35 +29,35 @@ public sealed partial record GetVersionResponseData /// [JsonPropertyName("availableRequests")] [Key("availableRequests")] - public System.Collections.Generic.List? AvailableRequests { get; init; } + public required System.Collections.Generic.List AvailableRequests { get; init; } /// /// Current OBS Studio version /// [JsonPropertyName("obsVersion")] [Key("obsVersion")] - public string? ObsVersion { get; init; } + public required string ObsVersion { get; init; } /// /// Current obs-websocket version /// [JsonPropertyName("obsWebSocketVersion")] [Key("obsWebSocketVersion")] - public string? ObsWebSocketVersion { get; init; } + public required string ObsWebSocketVersion { get; init; } /// /// Name of the platform. Usually `windows`, `macos`, or `ubuntu` (linux flavor). Not guaranteed to be any of those /// [JsonPropertyName("platform")] [Key("platform")] - public string? Platform { get; init; } + public required string Platform { get; init; } /// /// Description of the platform, like `Windows 10 (10.0)` /// [JsonPropertyName("platformDescription")] [Key("platformDescription")] - public string? PlatformDescription { get; init; } + public required string PlatformDescription { get; init; } /// /// Current latest obs-websocket RPC version @@ -71,7 +71,7 @@ public sealed partial record GetVersionResponseData /// [JsonPropertyName("supportedImageFormats")] [Key("supportedImageFormats")] - public System.Collections.Generic.List? SupportedImageFormats { get; init; } + public required System.Collections.Generic.List SupportedImageFormats { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -82,7 +82,7 @@ public GetVersionResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetVersionResponseData(int rpcVersion, string? obsVersion = null, string? obsWebSocketVersion = null, System.Collections.Generic.List? availableRequests = null, System.Collections.Generic.List? supportedImageFormats = null, string? platform = null, string? platformDescription = null) + public GetVersionResponseData(string obsVersion, string obsWebSocketVersion, int rpcVersion, System.Collections.Generic.List availableRequests, System.Collections.Generic.List supportedImageFormats, string platform, string platformDescription) { this.ObsVersion = obsVersion; this.ObsWebSocketVersion = obsWebSocketVersion; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/StopRecord.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/StopRecord.Response.g.cs index 04d885d..d79896b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/StopRecord.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/StopRecord.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record StopRecordResponseData /// [JsonPropertyName("outputPath")] [Key("outputPath")] - public string? OutputPath { get; init; } + public required string OutputPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public StopRecordResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public StopRecordResponseData(string? outputPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public StopRecordResponseData(string outputPath) { this.OutputPath = outputPath; } diff --git a/ObsWebSocket.Core/Generated/Serialization/ObsWebSocketPayloadSchema.g.cs b/ObsWebSocket.Core/Generated/Serialization/ObsWebSocketPayloadSchema.g.cs new file mode 100644 index 0000000..f782d10 --- /dev/null +++ b/ObsWebSocket.Core/Generated/Serialization/ObsWebSocketPayloadSchema.g.cs @@ -0,0 +1,99 @@ +// +// Wire keys per response record +#nullable enable + +using System; +using System.Collections.Generic; + +namespace ObsWebSocket.Core.Serialization; + +/// +/// The field names OBS sends for each response record, used to reject a payload being +/// read as a record it did not come from. +/// +internal static class ObsWebSocketPayloadSchema +{ + private static readonly Dictionary s_keys = new(StringComparer.Ordinal) + { + ["GetCanvasListResponseData"] = ["canvases"], + ["GetPersistentDataResponseData"] = ["slotValue"], + ["GetSceneCollectionListResponseData"] = ["currentSceneCollectionName", "sceneCollections"], + ["GetProfileListResponseData"] = ["currentProfileName", "profiles"], + ["GetProfileParameterResponseData"] = ["defaultParameterValue", "parameterValue"], + ["GetVideoSettingsResponseData"] = ["baseHeight", "baseWidth", "fpsDenominator", "fpsNumerator", "outputHeight", "outputWidth"], + ["GetStreamServiceSettingsResponseData"] = ["streamServiceSettings", "streamServiceType"], + ["GetRecordDirectoryResponseData"] = ["recordDirectory"], + ["GetSourceFilterKindListResponseData"] = ["sourceFilterKinds"], + ["GetSourceFilterListResponseData"] = ["filters"], + ["GetSourceFilterDefaultSettingsResponseData"] = ["defaultFilterSettings"], + ["GetSourceFilterResponseData"] = ["filterEnabled", "filterIndex", "filterKind", "filterSettings"], + ["GetVersionResponseData"] = ["availableRequests", "obsVersion", "obsWebSocketVersion", "platform", "platformDescription", "rpcVersion", "supportedImageFormats"], + ["GetStatsResponseData"] = ["activeFps", "availableDiskSpace", "averageFrameRenderTime", "cpuUsage", "memoryUsage", "outputSkippedFrames", "outputTotalFrames", "renderSkippedFrames", "renderTotalFrames", "webSocketSessionIncomingMessages", "webSocketSessionOutgoingMessages"], + ["CallVendorRequestResponseData"] = ["requestType", "responseData", "vendorName"], + ["GetHotkeyListResponseData"] = ["hotkeys"], + ["GetInputListResponseData"] = ["inputs"], + ["GetInputKindListResponseData"] = ["inputKinds"], + ["GetSpecialInputsResponseData"] = ["desktop1", "desktop2", "mic1", "mic2", "mic3", "mic4"], + ["CreateInputResponseData"] = ["inputUuid", "sceneItemId"], + ["GetInputDefaultSettingsResponseData"] = ["defaultInputSettings"], + ["GetInputSettingsResponseData"] = ["inputKind", "inputSettings"], + ["GetInputMuteResponseData"] = ["inputMuted"], + ["ToggleInputMuteResponseData"] = ["inputMuted"], + ["GetInputVolumeResponseData"] = ["inputVolumeDb", "inputVolumeMul"], + ["GetInputAudioBalanceResponseData"] = ["inputAudioBalance"], + ["GetInputAudioSyncOffsetResponseData"] = ["inputAudioSyncOffset"], + ["GetInputAudioMonitorTypeResponseData"] = ["monitorType"], + ["GetInputAudioTracksResponseData"] = ["inputAudioTracks"], + ["GetInputDeinterlaceModeResponseData"] = ["inputDeinterlaceMode"], + ["GetInputDeinterlaceFieldOrderResponseData"] = ["inputDeinterlaceFieldOrder"], + ["GetInputPropertiesListPropertyItemsResponseData"] = ["propertyItems"], + ["GetMediaInputStatusResponseData"] = ["mediaCursor", "mediaDuration", "mediaState"], + ["GetVirtualCamStatusResponseData"] = ["outputActive"], + ["ToggleVirtualCamResponseData"] = ["outputActive"], + ["GetReplayBufferStatusResponseData"] = ["outputActive"], + ["ToggleReplayBufferResponseData"] = ["outputActive"], + ["GetLastReplayBufferReplayResponseData"] = ["savedReplayPath"], + ["GetOutputListResponseData"] = ["outputs"], + ["GetOutputStatusResponseData"] = ["outputActive", "outputBytes", "outputCongestion", "outputDuration", "outputReconnecting", "outputSkippedFrames", "outputTimecode", "outputTotalFrames"], + ["ToggleOutputResponseData"] = ["outputActive"], + ["GetOutputSettingsResponseData"] = ["outputSettings"], + ["GetRecordStatusResponseData"] = ["outputActive", "outputBytes", "outputDuration", "outputPaused", "outputTimecode"], + ["ToggleRecordResponseData"] = ["outputActive"], + ["StopRecordResponseData"] = ["outputPath"], + ["GetSceneItemListResponseData"] = ["sceneItems"], + ["GetGroupSceneItemListResponseData"] = ["sceneItems"], + ["GetSceneItemIdResponseData"] = ["sceneItemId"], + ["GetSceneItemSourceResponseData"] = ["sourceName", "sourceUuid"], + ["CreateSceneItemResponseData"] = ["sceneItemId"], + ["DuplicateSceneItemResponseData"] = ["sceneItemId"], + ["GetSceneItemTransformResponseData"] = ["sceneItemTransform"], + ["GetSceneItemEnabledResponseData"] = ["sceneItemEnabled"], + ["GetSceneItemLockedResponseData"] = ["sceneItemLocked"], + ["GetSceneItemIndexResponseData"] = ["sceneItemIndex"], + ["GetSceneItemBlendModeResponseData"] = ["sceneItemBlendMode"], + ["GetSceneListResponseData"] = ["currentPreviewSceneName", "currentPreviewSceneUuid", "currentProgramSceneName", "currentProgramSceneUuid", "scenes"], + ["GetGroupListResponseData"] = ["groups"], + ["GetCurrentProgramSceneResponseData"] = ["currentProgramSceneName", "currentProgramSceneUuid", "sceneName", "sceneUuid"], + ["GetCurrentPreviewSceneResponseData"] = ["currentPreviewSceneName", "currentPreviewSceneUuid", "sceneName", "sceneUuid"], + ["CreateSceneResponseData"] = ["sceneUuid"], + ["GetSceneSceneTransitionOverrideResponseData"] = ["transitionDuration", "transitionName"], + ["GetSourceActiveResponseData"] = ["videoActive", "videoShowing"], + ["GetSourceScreenshotResponseData"] = ["imageData"], + ["GetStreamStatusResponseData"] = ["outputActive", "outputBytes", "outputCongestion", "outputDuration", "outputReconnecting", "outputSkippedFrames", "outputTimecode", "outputTotalFrames"], + ["ToggleStreamResponseData"] = ["outputActive"], + ["GetTransitionKindListResponseData"] = ["transitionKinds"], + ["GetSceneTransitionListResponseData"] = ["currentSceneTransitionKind", "currentSceneTransitionName", "currentSceneTransitionUuid", "transitions"], + ["GetCurrentSceneTransitionResponseData"] = ["transitionConfigurable", "transitionDuration", "transitionFixed", "transitionKind", "transitionName", "transitionSettings", "transitionUuid"], + ["GetCurrentSceneTransitionCursorResponseData"] = ["transitionCursor"], + ["GetStudioModeEnabledResponseData"] = ["studioModeEnabled"], + ["GetMonitorListResponseData"] = ["monitors"], + }; + + /// + /// The keys a response record expects, or an empty span when the record is unknown + /// to the schema, in which case no check is possible. + /// + /// The record's type name. + public static string[] KnownKeys(string responseTypeName) => + s_keys.TryGetValue(responseTypeName, out string[]? keys) ? keys : []; +} diff --git a/ObsWebSocket.Core/Groups/ConfigGroup.cs b/ObsWebSocket.Core/Groups/ConfigGroup.cs index db8d7cf..fc1cc8c 100644 --- a/ObsWebSocket.Core/Groups/ConfigGroup.cs +++ b/ObsWebSocket.Core/Groups/ConfigGroup.cs @@ -57,7 +57,7 @@ public readonly partial struct ConfigGroup public Task GetStreamServiceSettingsAsync(CancellationToken cancellationToken = default) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Config.GetStreamServiceSettingsAsync(typeInfo, cancellationToken); } @@ -112,7 +112,7 @@ public Task SetStreamServiceSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Config.SetStreamServiceSettingsAsync( streamServiceType, settings, diff --git a/ObsWebSocket.Core/Groups/FiltersGroup.cs b/ObsWebSocket.Core/Groups/FiltersGroup.cs index f70830c..e164b64 100644 --- a/ObsWebSocket.Core/Groups/FiltersGroup.cs +++ b/ObsWebSocket.Core/Groups/FiltersGroup.cs @@ -99,7 +99,7 @@ public readonly partial struct FiltersGroup ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Filters.GetSourceFilterSettingsAsync( sourceName, filterName, @@ -183,7 +183,7 @@ public Task SetSourceFilterSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Filters.SetSourceFilterSettingsAsync( sourceName, filterName, @@ -270,7 +270,7 @@ public Task CreateSourceFilterAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Filters.CreateSourceFilterAsync( sourceName, filterName, @@ -329,7 +329,7 @@ public Task CreateSourceFilterAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Filters.GetSourceFilterDefaultSettingsAsync( filterKind, typeInfo, diff --git a/ObsWebSocket.Core/Groups/InputsGroup.cs b/ObsWebSocket.Core/Groups/InputsGroup.cs index e68272b..5307ff0 100644 --- a/ObsWebSocket.Core/Groups/InputsGroup.cs +++ b/ObsWebSocket.Core/Groups/InputsGroup.cs @@ -185,7 +185,7 @@ public async Task SetInputMutesAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Inputs.GetInputSettingsAsync(inputName, typeInfo, cancellationToken); } @@ -258,7 +258,7 @@ public Task SetInputSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Inputs.SetInputSettingsAsync( inputName, settings, @@ -355,7 +355,7 @@ public Task SetInputSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Inputs.CreateInputAsync( inputKind, inputName, @@ -416,7 +416,7 @@ public Task SetInputSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Inputs.GetInputDefaultSettingsAsync(inputKind, typeInfo, cancellationToken); } diff --git a/ObsWebSocket.Core/Groups/MediaInputsGroup.cs b/ObsWebSocket.Core/Groups/MediaInputsGroup.cs index 0c4f224..c6c1046 100644 --- a/ObsWebSocket.Core/Groups/MediaInputsGroup.cs +++ b/ObsWebSocket.Core/Groups/MediaInputsGroup.cs @@ -42,7 +42,7 @@ await client new TriggerMediaInputActionRequestData { InputName = inputName, - MediaAction = action.ToWireValue(), + MediaAction = action, }, cancellationToken ) diff --git a/ObsWebSocket.Core/Groups/OutputsGroup.cs b/ObsWebSocket.Core/Groups/OutputsGroup.cs index 51ff2d5..21fcce1 100644 --- a/ObsWebSocket.Core/Groups/OutputsGroup.cs +++ b/ObsWebSocket.Core/Groups/OutputsGroup.cs @@ -67,7 +67,7 @@ public readonly partial struct OutputsGroup ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Outputs.GetOutputSettingsAsync(outputName, typeInfo, cancellationToken); } @@ -122,7 +122,7 @@ public Task SetOutputSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Outputs.SetOutputSettingsAsync( outputName, settings, diff --git a/ObsWebSocket.Core/Groups/RecordGroup.cs b/ObsWebSocket.Core/Groups/RecordGroup.cs index 86566e8..5ca831a 100644 --- a/ObsWebSocket.Core/Groups/RecordGroup.cs +++ b/ObsWebSocket.Core/Groups/RecordGroup.cs @@ -59,7 +59,7 @@ public readonly partial struct RecordGroup try { RecordStateChangedEventArgs ev = await waitTask.ConfigureAwait(false); - return OutputStateExtensions.FromWireValue(ev.EventData.OutputState); + return ev.EventData.OutputState; } catch (TimeoutException) { diff --git a/ObsWebSocket.Core/Groups/ScenesGroup.cs b/ObsWebSocket.Core/Groups/ScenesGroup.cs index 4ffc9f7..8d370b6 100644 --- a/ObsWebSocket.Core/Groups/ScenesGroup.cs +++ b/ObsWebSocket.Core/Groups/ScenesGroup.cs @@ -226,7 +226,7 @@ public async Task SceneExistsAsync( client.EnsureConnected(); GetSceneListResponseData? scenes = await client - .Scenes.GetSceneListAsync(new GetSceneListRequestData(), cancellationToken) + .Scenes.GetSceneListAsync(new(), cancellationToken) .ConfigureAwait(false); return scenes?.Scenes?.Any(s => diff --git a/ObsWebSocket.Core/Groups/StreamGroup.cs b/ObsWebSocket.Core/Groups/StreamGroup.cs index 74ef2b7..f3f5eb3 100644 --- a/ObsWebSocket.Core/Groups/StreamGroup.cs +++ b/ObsWebSocket.Core/Groups/StreamGroup.cs @@ -58,7 +58,7 @@ public readonly partial struct StreamGroup try { StreamStateChangedEventArgs ev = await waitTask.ConfigureAwait(false); - return OutputStateExtensions.FromWireValue(ev.EventData.OutputState); + return ev.EventData.OutputState; } catch (TimeoutException) { diff --git a/ObsWebSocket.Core/Groups/TransitionsGroup.cs b/ObsWebSocket.Core/Groups/TransitionsGroup.cs index 15bd666..61238eb 100644 --- a/ObsWebSocket.Core/Groups/TransitionsGroup.cs +++ b/ObsWebSocket.Core/Groups/TransitionsGroup.cs @@ -59,7 +59,7 @@ public readonly partial struct TransitionsGroup ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Transitions.GetCurrentSceneTransitionSettingsAsync( typeInfo, cancellationToken @@ -116,7 +116,7 @@ public Task SetCurrentSceneTransitionSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Transitions.SetCurrentSceneTransitionSettingsAsync( settings, typeInfo, diff --git a/ObsWebSocket.Core/ObsWebSocketClient.cs b/ObsWebSocket.Core/ObsWebSocketClient.cs index fe52b64..b07c92e 100644 --- a/ObsWebSocket.Core/ObsWebSocketClient.cs +++ b/ObsWebSocket.Core/ObsWebSocketClient.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Frozen; using System.Diagnostics; @@ -6,6 +6,7 @@ using System.Net.WebSockets; using System.Runtime.CompilerServices; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ObsWebSocket.Core.Events; @@ -321,6 +322,11 @@ await SendMessageAsync( /// The expected response data type. /// The OBS request type string. /// The request payload, or . + /// + /// Metadata for , for a type this library does not know. + /// Supplying it from your own JsonSerializerContext keeps the call AOT safe and + /// avoids hand building a . + /// /// Optional override for the request timeout. /// A token to cancel the operation. /// The response data. @@ -331,11 +337,18 @@ await SendMessageAsync( public async Task CallRequiredAsync( string requestType, object? requestData = null, + JsonTypeInfo? requestTypeInfo = null, int? timeoutMs = null, CancellationToken cancellationToken = default ) where TResponse : class => - await CallAsync(requestType, requestData, timeoutMs, cancellationToken) + await CallAsync( + requestType, + requestData, + requestTypeInfo, + timeoutMs, + cancellationToken + ) .ConfigureAwait(false) ?? throw new ObsWebSocketException( $"OBS reported success for '{requestType}' but returned no {typeof(TResponse).Name} payload." @@ -348,6 +361,11 @@ await CallAsync(requestType, requestData, timeoutMs, cancellationToke /// The expected type of the response data payload (must be a reference type). /// The OBS WebSocket request type string. /// Optional data payload for the request. Should be serializable to the format expected by OBS for the request type. + /// + /// Metadata for , for a type this library does not know. + /// Supplying it from your own JsonSerializerContext keeps the call AOT safe and + /// avoids hand building a . + /// /// Optional timeout in milliseconds to wait for the response. Defaults to . /// A token to cancel the asynchronous operation. /// @@ -361,6 +379,7 @@ await CallAsync(requestType, requestData, timeoutMs, cancellationToke public async Task CallAsync( string requestType, object? requestData = null, + JsonTypeInfo? requestTypeInfo = null, int? timeoutMs = null, CancellationToken cancellationToken = default ) @@ -403,7 +422,7 @@ await SendMessageAsync( new RequestPayload( requestType, requestId, - SerializeRequestData(requestType, requestData) + SerializeRequestData(requestType, requestData, requestTypeInfo) ), linkedCts.Token ) @@ -469,6 +488,11 @@ await SendMessageAsync( /// The expected type of the response data payload (must be a value type). /// The OBS WebSocket request type string. /// Optional data payload for the request. Should be serializable to the format expected by OBS for the request type. + /// + /// Metadata for , for a type this library does not know. + /// Supplying it from your own JsonSerializerContext keeps the call AOT safe and + /// avoids hand building a . + /// /// Optional timeout in milliseconds to wait for the response. Defaults to . /// A token to cancel the asynchronous operation. /// @@ -482,6 +506,7 @@ await SendMessageAsync( public async Task CallAsyncValue( string requestType, object? requestData = null, + JsonTypeInfo? requestTypeInfo = null, int? timeoutMs = null, CancellationToken cancellationToken = default ) @@ -510,7 +535,7 @@ await SendMessageAsync( new RequestPayload( requestType, requestId, - SerializeRequestData(requestType, requestData) + SerializeRequestData(requestType, requestData, requestTypeInfo) ), linkedCts.Token ) @@ -2179,7 +2204,16 @@ private TPayload ExtractPayloadFromHandshake(object messageObject, str /// In Native AOT, arbitrary objects passed through the batch API path may fail if they are not registered /// in . /// - private static JsonElement? SerializeRequestData(string requestContext, object? requestData) + /// + /// Metadata for , for a type this library does not know. + /// Supplying it from your own JsonSerializerContext keeps the call AOT safe and + /// avoids hand building a . + /// + private static JsonElement? SerializeRequestData( + string requestContext, + object? requestData, + JsonTypeInfo? requestTypeInfo = null + ) { if (requestData is null) { @@ -2188,12 +2222,17 @@ private TPayload ExtractPayloadFromHandshake(object messageObject, str try { - return requestData is JsonElement element - ? element - : JsonSerializer.SerializeToElement( - requestData, - s_payloadJsonOptions.GetTypeInfo(requestData.GetType()) - ); + if (requestData is JsonElement element) + { + return element; + } + + // A caller passing metadata from their own context can send a type this library has + // never heard of, without hand rolling a JsonDocument, and stays AOT safe doing it. + return JsonSerializer.SerializeToElement( + requestData, + requestTypeInfo ?? s_payloadJsonOptions.GetTypeInfo(requestData.GetType()) + ); } catch (InvalidOperationException ex) { diff --git a/ObsWebSocket.Core/ObsWebSocketClient.Helper.Convenience.cs b/ObsWebSocket.Core/ObsWebSocketClientOperations.Conveniences.cs similarity index 99% rename from ObsWebSocket.Core/ObsWebSocketClient.Helper.Convenience.cs rename to ObsWebSocket.Core/ObsWebSocketClientOperations.Conveniences.cs index 6b993f1..c38ec47 100644 --- a/ObsWebSocket.Core/ObsWebSocketClient.Helper.Convenience.cs +++ b/ObsWebSocket.Core/ObsWebSocketClientOperations.Conveniences.cs @@ -8,7 +8,7 @@ namespace ObsWebSocket.Core; /// The client level conveniences: waiting for an event, and sending a batch. Neither belongs to /// one protocol category, so both stay on the client rather than on a category group. /// -public static partial class ObsWebSocketClientHelpers +public static partial class ObsWebSocketClientOperations { extension(ObsWebSocketClient client) { diff --git a/ObsWebSocket.Core/ObsWebSocketClient.Helper.cs b/ObsWebSocket.Core/ObsWebSocketClientOperations.cs similarity index 97% rename from ObsWebSocket.Core/ObsWebSocketClient.Helper.cs rename to ObsWebSocket.Core/ObsWebSocketClientOperations.cs index df5762b..2dbafb7 100644 --- a/ObsWebSocket.Core/ObsWebSocketClient.Helper.cs +++ b/ObsWebSocket.Core/ObsWebSocketClientOperations.cs @@ -9,7 +9,7 @@ namespace ObsWebSocket.Core; /// WaitForEventAsync. Everything category scoped lives on the category group instead, so /// client.Scenes, client.Inputs and the rest are where those methods are found. /// -public static partial class ObsWebSocketClientHelpers +public static partial class ObsWebSocketClientOperations { private static readonly JsonSerializerOptions s_helperJsonOptions = CreateHelperOptions(); diff --git a/ObsWebSocket.Core/ObsWebSocketRequestException.cs b/ObsWebSocket.Core/ObsWebSocketRequestException.cs index dbfe5a9..8988a82 100644 --- a/ObsWebSocket.Core/ObsWebSocketRequestException.cs +++ b/ObsWebSocket.Core/ObsWebSocketRequestException.cs @@ -25,7 +25,7 @@ public ObsWebSocketRequestException( { RequestType = requestType; RequestId = requestId; - Status = status; + StatusCode = status is null ? null : (RequestStatusCode)status.Code; Comment = comment; } @@ -49,16 +49,13 @@ public ObsWebSocketRequestException(string message, Exception innerException) /// The identifier of the rejected request. public string RequestId { get; } = string.Empty; - /// The status OBS reported, when one was available. - public RequestStatus? Status { get; } - /// The comment OBS attached, if any. public string? Comment { get; } /// - /// The status OBS reported as - /// , so a handler can - /// match on the reason rather than on the text of . + /// The reason OBS gave for rejecting the request, so a handler matches on the reason rather + /// than on the text of . A code this build does not know still + /// round trips, since the enum carries the number: cast it back to . /// /// /// @@ -66,7 +63,7 @@ public ObsWebSocketRequestException(string message, Exception innerException) /// when (ex.StatusCode is RequestStatusCode.ResourceNotFound) { } /// /// - public RequestStatusCode? StatusCode => Status is null ? null : (RequestStatusCode)Status.Code; + public RequestStatusCode? StatusCode { get; } } /// diff --git a/ObsWebSocket.Core/Protocol/Common/StubTypes.cs b/ObsWebSocket.Core/Protocol/Common/StubTypes.cs index a5da120..c2fadd3 100644 --- a/ObsWebSocket.Core/Protocol/Common/StubTypes.cs +++ b/ObsWebSocket.Core/Protocol/Common/StubTypes.cs @@ -14,17 +14,17 @@ public sealed class SceneStub /// Scene name. [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// Scene UUID. [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Scene index position. [JsonPropertyName("sceneIndex")] [Key("sceneIndex")] - public double? SceneIndex { get; init; } + public required int SceneIndex { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -48,126 +48,126 @@ public sealed class SceneItemTransformStub /// [JsonPropertyName("positionX")] [Key("positionX")] - public double? PositionX { get; init; } + public required double PositionX { get; init; } /// /// Position X value. /// [JsonPropertyName("positionY")] [Key("positionY")] - public double? PositionY { get; init; } + public required double PositionY { get; init; } /// /// Rotation value. /// [JsonPropertyName("rotation")] [Key("rotation")] - public double? Rotation { get; init; } + public required double Rotation { get; init; } /// /// Scale X value. /// [JsonPropertyName("scaleX")] [Key("scaleX")] - public double? ScaleX { get; init; } + public required double ScaleX { get; init; } /// /// Scale Y value. /// [JsonPropertyName("scaleY")] [Key("scaleY")] - public double? ScaleY { get; init; } + public required double ScaleY { get; init; } /// /// Width value. /// [JsonPropertyName("width")] [Key("width")] - public double? Width { get; init; } + public required double Width { get; init; } /// /// Height value. /// [JsonPropertyName("height")] [Key("height")] - public double? Height { get; init; } + public required double Height { get; init; } /// /// Source width value. /// [JsonPropertyName("sourceWidth")] [Key("sourceWidth")] - public double? SourceWidth { get; init; } + public required double SourceWidth { get; init; } /// /// Source height value. /// [JsonPropertyName("sourceHeight")] [Key("sourceHeight")] - public double? SourceHeight { get; init; } + public required double SourceHeight { get; init; } /// /// Alignment value. /// [JsonPropertyName("alignment")] [Key("alignment")] - public double? Alignment { get; init; } + public required int Alignment { get; init; } /// /// Bounds type value. /// [JsonPropertyName("boundsType")] [Key("boundsType")] - public string? BoundsType { get; init; } + public required string BoundsType { get; init; } /// /// Bounds alignment value. /// [JsonPropertyName("boundsAlignment")] [Key("boundsAlignment")] - public double? BoundsAlignment { get; init; } + public required int BoundsAlignment { get; init; } /// /// Bounds width value. /// [JsonPropertyName("boundsWidth")] [Key("boundsWidth")] - public double? BoundsWidth { get; init; } + public required double BoundsWidth { get; init; } /// /// Bounds height value. /// [JsonPropertyName("boundsHeight")] [Key("boundsHeight")] - public double? BoundsHeight { get; init; } + public required double BoundsHeight { get; init; } /// /// Crop left value. /// [JsonPropertyName("cropLeft")] [Key("cropLeft")] - public double? CropLeft { get; init; } + public required int CropLeft { get; init; } /// /// Crop top value. /// [JsonPropertyName("cropTop")] [Key("cropTop")] - public double? CropTop { get; init; } + public required int CropTop { get; init; } /// /// Crop right value. /// [JsonPropertyName("cropRight")] [Key("cropRight")] - public double? CropRight { get; init; } + public required int CropRight { get; init; } /// /// Crop bottom value. /// [JsonPropertyName("cropBottom")] [Key("cropBottom")] - public double? CropBottom { get; init; } + public required int CropBottom { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -189,32 +189,32 @@ public sealed class SceneItemStub /// Scene item ID. [JsonPropertyName("sceneItemId")] [Key("sceneItemId")] - public double? SceneItemId { get; init; } + public required int SceneItemId { get; init; } /// Scene item index position. [JsonPropertyName("sceneItemIndex")] [Key("sceneItemIndex")] - public double? SceneItemIndex { get; init; } + public required int SceneItemIndex { get; init; } /// Name of the source associated with the scene item. [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// UUID of the source associated with the scene item. [JsonPropertyName("sourceUuid")] [Key("sourceUuid")] - public string? SourceUuid { get; init; } + public required string SourceUuid { get; init; } /// Whether the scene item is enabled (visible). [JsonPropertyName("sceneItemEnabled")] [Key("sceneItemEnabled")] - public bool? SceneItemEnabled { get; init; } + public required bool SceneItemEnabled { get; init; } /// Whether the scene item is locked. [JsonPropertyName("sceneItemLocked")] [Key("sceneItemLocked")] - public bool? SceneItemLocked { get; init; } + public required bool SceneItemLocked { get; init; } /// Whether the source is a group. [JsonPropertyName("isGroup")] @@ -224,7 +224,7 @@ public sealed class SceneItemStub /// Transform data for the scene item. [JsonPropertyName("sceneItemTransform")] [Key("sceneItemTransform")] - public SceneItemTransformStub? SceneItemTransform { get; init; } // Made nullable for safety + public required SceneItemTransformStub SceneItemTransform { get; init; } // Made nullable for safety /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -246,22 +246,22 @@ public sealed class FilterStub /// Filter name. [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// Filter kind. [JsonPropertyName("filterKind")] [Key("filterKind")] - public string? FilterKind { get; init; } + public required string FilterKind { get; init; } /// Filter index position. [JsonPropertyName("filterIndex")] [Key("filterIndex")] - public double? FilterIndex { get; init; } + public required int FilterIndex { get; init; } /// Whether the filter is enabled. [JsonPropertyName("filterEnabled")] [Key("filterEnabled")] - public bool? FilterEnabled { get; init; } + public required bool FilterEnabled { get; init; } /// Filter settings object. [JsonPropertyName("filterSettings")] @@ -288,22 +288,22 @@ public sealed class InputStub /// Input name. [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// Input UUID. [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Input kind. [JsonPropertyName("inputKind")] [Key("inputKind")] - public string? InputKind { get; init; } + public required string InputKind { get; init; } /// Unversioned input kind. [JsonPropertyName("unversionedInputKind")] [Key("unversionedInputKind")] - public string? UnversionedInputKind { get; init; } + public required string UnversionedInputKind { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -325,27 +325,27 @@ public sealed class TransitionStub /// Transition name. [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// Transition UUID. [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Transition kind. [JsonPropertyName("transitionKind")] [Key("transitionKind")] - public string? TransitionKind { get; init; } + public required string TransitionKind { get; init; } /// Whether the transition is configurable. [JsonPropertyName("transitionConfigurable")] [Key("transitionConfigurable")] - public bool? TransitionConfigurable { get; init; } + public required bool TransitionConfigurable { get; init; } /// Whether the transition duration is fixed. [JsonPropertyName("transitionFixed")] [Key("transitionFixed")] - public bool? TransitionFixed { get; init; } + public required bool TransitionFixed { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -367,27 +367,27 @@ public sealed class OutputStub /// Output name. [JsonPropertyName("outputName")] [Key("outputName")] - public string? OutputName { get; init; } + public required string OutputName { get; init; } /// Output kind. [JsonPropertyName("outputKind")] [Key("outputKind")] - public string? OutputKind { get; init; } + public required string OutputKind { get; init; } /// Whether the output is active. [JsonPropertyName("outputActive")] [Key("outputActive")] - public bool? OutputActive { get; init; } + public required bool OutputActive { get; init; } /// Output width. [JsonPropertyName("outputWidth")] [Key("outputWidth")] - public double? OutputWidth { get; init; } + public required int OutputWidth { get; init; } /// Output height. [JsonPropertyName("outputHeight")] [Key("outputHeight")] - public double? OutputHeight { get; init; } + public required int OutputHeight { get; init; } /// Output settings. [JsonPropertyName("outputSettings")] @@ -414,32 +414,32 @@ public sealed class MonitorStub /// Monitor name. [JsonPropertyName("monitorName")] [Key("monitorName")] - public string? MonitorName { get; init; } + public required string MonitorName { get; init; } /// Monitor index. [JsonPropertyName("monitorIndex")] [Key("monitorIndex")] - public double? MonitorIndex { get; init; } + public required int MonitorIndex { get; init; } /// Monitor width. [JsonPropertyName("monitorWidth")] [Key("monitorWidth")] - public double? MonitorWidth { get; init; } + public required int MonitorWidth { get; init; } /// Monitor height. [JsonPropertyName("monitorHeight")] [Key("monitorHeight")] - public double? MonitorHeight { get; init; } + public required int MonitorHeight { get; init; } /// Monitor position X. [JsonPropertyName("monitorPositionX")] [Key("monitorPositionX")] - public double? MonitorPositionX { get; init; } + public required int MonitorPositionX { get; init; } /// Monitor position Y. [JsonPropertyName("monitorPositionY")] [Key("monitorPositionY")] - public double? MonitorPositionY { get; init; } + public required int MonitorPositionY { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] diff --git a/ObsWebSocket.Core/Serialization/MsgPackJsonElementResolver.cs b/ObsWebSocket.Core/Serialization/MsgPackJsonElementResolver.cs index b8547c7..970b990 100644 --- a/ObsWebSocket.Core/Serialization/MsgPackJsonElementResolver.cs +++ b/ObsWebSocket.Core/Serialization/MsgPackJsonElementResolver.cs @@ -16,6 +16,11 @@ private MsgPackJsonElementResolver() { } ? (IMessagePackFormatter)(object)JsonElementFormatter.Instance : typeof(T) == typeof(JsonElement?) ? (IMessagePackFormatter)(object)NullableJsonElementFormatter.Instance + // An array the protocol does not give an item type for becomes List, and + // nothing else in the resolver chain knows how to build one, so GetCanvasList could not + // be read at all over MessagePack. + : typeof(T) == typeof(List) + ? (IMessagePackFormatter)(object)JsonElementListFormatter.Instance : null; internal sealed class JsonElementFormatter : IMessagePackFormatter @@ -101,4 +106,56 @@ MessagePackSerializerOptions options ? null : JsonElementFormatter.Instance.Deserialize(ref reader, options); } + + /// + /// Reads and writes a list of , which is what an array whose item + /// type the protocol does not state is generated as. + /// + internal sealed class JsonElementListFormatter : IMessagePackFormatter?> + { + public static readonly JsonElementListFormatter Instance = new(); + + /// + public void Serialize( + ref MessagePackWriter writer, + List? value, + MessagePackSerializerOptions options + ) + { + if (value is null) + { + writer.WriteNil(); + return; + } + + writer.WriteArrayHeader(value.Count); + foreach (JsonElement item in value) + { + JsonElementFormatter.Instance.Serialize(ref writer, item, options); + } + } + + /// + public List? Deserialize( + ref MessagePackReader reader, + MessagePackSerializerOptions options + ) + { + if (reader.TryReadNil()) + { + return null; + } + + options.Security.DepthStep(ref reader); + int count = reader.ReadArrayHeader(); + List items = new(count); + for (int i = 0; i < count; i++) + { + items.Add(JsonElementFormatter.Instance.Deserialize(ref reader, options)); + } + + reader.Depth--; + return items; + } + } } diff --git a/ObsWebSocket.Core/Serialization/PayloadShape.cs b/ObsWebSocket.Core/Serialization/PayloadShape.cs new file mode 100644 index 0000000..e1ee279 --- /dev/null +++ b/ObsWebSocket.Core/Serialization/PayloadShape.cs @@ -0,0 +1,104 @@ +using System.Text.Json; +using MessagePack; + +namespace ObsWebSocket.Core.Serialization; + +/// +/// Checks a batch payload against the record it is about to be read as. +/// +/// +/// Reading a payload as the wrong record is silent on MessagePack, which maps by key name and +/// leaves everything unmatched at its default, and silent on JSON too for the response records that +/// have no required member, which is most of them. Response records almost never share field +/// names, so a payload carrying none of the target's keys did not come from that request. +/// +/// This rejects rather than identifies. Five response shapes are shared by more than one request, +/// but those records are field for field identical, so reading one as another yields the right +/// values anyway. A payload that overlaps the target only partly still passes, which is the +/// remaining gap. +/// +/// +internal static class PayloadShape +{ + /// + /// Throws when a payload carries none of the fields expects. + /// + /// The record the payload is about to be read as. + /// The transport shaped payload. + /// Thrown when the payload cannot be that record. + public static void EnsurePlausible(object responseData) + where TResponse : class + { + string[] expected = ObsWebSocketPayloadSchema.KnownKeys(typeof(TResponse).Name); + if (expected.Length == 0) + { + return; + } + + bool anyMatch = responseData switch + { + JsonElement json => MatchesJson(json, expected), + ReadOnlyMemory packed => MatchesMsgPack(packed, expected), + _ => true, + }; + + if (!anyMatch) + { + throw new ObsWebSocketSerializationException( + $"This payload carries none of the fields {typeof(TResponse).Name} expects " + + $"({string.Join(", ", expected)}), so it came from a different request. Under " + + "RequestBatchExecutionType.Parallel OBS labels each result with another " + + "request's type, which is the usual cause." + ); + } + } + + private static bool MatchesJson(JsonElement json, string[] expected) + { + if (json.ValueKind != JsonValueKind.Object) + { + return true; + } + + bool sawAny = false; + foreach (JsonProperty property in json.EnumerateObject()) + { + sawAny = true; + if (Array.IndexOf(expected, property.Name) >= 0) + { + return true; + } + } + + // An empty object tells us nothing either way. + return !sawAny; + } + + private static bool MatchesMsgPack(ReadOnlyMemory packed, string[] expected) + { + MessagePackReader reader = new(packed); + if (reader.NextMessagePackType != MessagePackType.Map) + { + return true; + } + + int count = reader.ReadMapHeader(); + if (count == 0) + { + return true; + } + + for (int i = 0; i < count; i++) + { + string? key = reader.ReadString(); + if (key is not null && Array.IndexOf(expected, key) >= 0) + { + return true; + } + + reader.Skip(); + } + + return false; + } +} diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index dada299..1786d2a 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -14,6 +14,7 @@ using ObsWebSocket.Core.Protocol.Common; using ObsWebSocket.Core.Protocol.Common.FilterSettings; using ObsWebSocket.Core.Protocol.Common.InputSettings; +using ObsWebSocket.Core.Protocol.Common.NestedTypes; using ObsWebSocket.Core.Protocol.Generated; using ObsWebSocket.Core.Protocol.Requests; using ObsWebSocket.Core.Protocol.Responses; @@ -58,13 +59,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _obsClient.AuthenticationFailure += OnObsAuthenticationFailure; // --- Subscribe to Specific OBS Events --- - _obsClient.CurrentProgramSceneChanged += OnCurrentProgramSceneChanged; - _obsClient.InputMuteStateChanged += OnInputMuteStateChanged; - _obsClient.StudioModeStateChanged += OnStudioModeStateChanged; - _obsClient.InputCreated += OnInputCreated; - _obsClient.StreamStateChanged += OnStreamStateChanged; - _obsClient.SceneCreated += OnSceneCreated; - _obsClient.SourceFilterCreated += OnSourceFilterCreated; + _obsClient.Scenes.CurrentProgramSceneChanged += OnCurrentProgramSceneChanged; + _obsClient.Inputs.InputMuteStateChanged += OnInputMuteStateChanged; + _obsClient.Ui.StudioModeStateChanged += OnStudioModeStateChanged; + _obsClient.Inputs.InputCreated += OnInputCreated; + _obsClient.Outputs.StreamStateChanged += OnStreamStateChanged; + _obsClient.Scenes.SceneCreated += OnSceneCreated; + _obsClient.Filters.SourceFilterCreated += OnSourceFilterCreated; _logger.LogInformation("Example Worker running."); _logger.LogInformation( @@ -150,14 +151,14 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _obsClient.Disconnected -= OnObsDisconnected; _obsClient.ConnectionFailed -= OnObsConnectionFailed; _obsClient.AuthenticationFailure -= OnObsAuthenticationFailure; - _obsClient.CurrentProgramSceneChanged -= OnCurrentProgramSceneChanged; - _obsClient.InputMuteStateChanged -= OnInputMuteStateChanged; - _obsClient.StudioModeStateChanged -= OnStudioModeStateChanged; + _obsClient.Scenes.CurrentProgramSceneChanged -= OnCurrentProgramSceneChanged; + _obsClient.Inputs.InputMuteStateChanged -= OnInputMuteStateChanged; + _obsClient.Ui.StudioModeStateChanged -= OnStudioModeStateChanged; // Unsubscribe new handlers - _obsClient.InputCreated -= OnInputCreated; - _obsClient.StreamStateChanged -= OnStreamStateChanged; - _obsClient.SceneCreated -= OnSceneCreated; - _obsClient.SourceFilterCreated -= OnSourceFilterCreated; + _obsClient.Inputs.InputCreated -= OnInputCreated; + _obsClient.Outputs.StreamStateChanged -= OnStreamStateChanged; + _obsClient.Scenes.SceneCreated -= OnSceneCreated; + _obsClient.Filters.SourceFilterCreated -= OnSourceFilterCreated; // Ensure disconnection on exit if (_obsClient.IsConnected) @@ -454,7 +455,9 @@ await _obsClient.Filters.GetSourceFilterListAsync( _ = table.AddColumn("Enabled"); foreach (Core.Protocol.Common.FilterStub filterElement in filterList.Filters) { - string filterIndex = filterElement.FilterIndex?.ToString() ?? "N/A"; + string filterIndex = filterElement.FilterIndex.ToString( + System.Globalization.CultureInfo.InvariantCulture + ); string filterName = Markup.Escape(filterElement.FilterName ?? "N/A") ?? "N/A"; string filterKind = @@ -704,7 +707,7 @@ await _obsClient.MediaInputs.TriggerMediaActionAsync( { // Typed failure carries the protocol status, so no message matching. UiWarn( - $"OBS rejected {ex.RequestType} with code {ex.Status?.Code}: {ex.Comment}" + $"OBS rejected {ex.RequestType} with code {(int?)ex.StatusCode}: {ex.Comment}" ); } @@ -825,7 +828,7 @@ version is null int sceneCount = scenes?.Scenes?.Count ?? 0; GetInputListResponseData? inputs = await cycleClient - .Inputs.GetInputListAsync(new GetInputListRequestData(), cancellationToken) + .Inputs.GetInputListAsync(new(), cancellationToken) .ConfigureAwait(false); if (inputs?.Inputs is null || inputs.Inputs.Count == 0) { @@ -1337,7 +1340,7 @@ CancellationToken cancellationToken string inputName = $"__obsws_input_{suffix}"; GetSceneListResponseData? sceneList = await client - .Scenes.GetSceneListAsync(new GetSceneListRequestData(), cancellationToken) + .Scenes.GetSceneListAsync(new(), cancellationToken) .ConfigureAwait(false); string originalScene = sceneList?.CurrentProgramSceneName ?? string.Empty; @@ -1755,7 +1758,7 @@ await TrySettingsCheckAsync( } catch (ObsWebSocketRequestException ex) { - caught = $"code {ex.Status?.Code}"; + caught = $"code {(int?)ex.StatusCode}"; } // TryGet reports the failure without throwing. @@ -2364,6 +2367,715 @@ await client .ConfigureAwait(false) ); + results.Add( + await TrySettingsCheckAsync( + "Preview scene helpers", + async () => + { + // Preview only exists in Studio Mode, so turn it on for the check and + // put it back however it was found. + GetStudioModeEnabledResponseData studio = await client + .Ui.GetStudioModeEnabledAsync(cancellationToken) + .ConfigureAwait(false); + if (!studio.StudioModeEnabled) + { + await client + .Ui.SetStudioModeEnabledAsync(new(true), cancellationToken) + .ConfigureAwait(false); + } + + try + { + // Every switch waits for the event confirming it. OBS points + // Preview at the Program scene while enabling Studio Mode, and + // that lands after StudioModeStateChanged, so a switch that does + // not wait for its own confirmation gets silently undone. + await client + .Scenes.SwitchPreviewSceneAndWaitAsync( + sceneName, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + GetCurrentPreviewSceneResponseData preview = await client + .Scenes.GetCurrentPreviewSceneAsync(cancellationToken) + .ConfigureAwait(false); + + // The plain overload, confirmed by waiting on the event directly. + Task back = + client.WaitForEventAsync( + e => + string.Equals( + e.EventData.SceneName, + originalScene, + StringComparison.Ordinal + ), + TimeSpan.FromSeconds(5), + cancellationToken + ); + await client + .Scenes.SwitchPreviewSceneAsync( + originalScene, + cancellationToken + ) + .ConfigureAwait(false); + _ = await back.ConfigureAwait(false); + + GetCurrentPreviewSceneResponseData restored = await client + .Scenes.GetCurrentPreviewSceneAsync(cancellationToken) + .ConfigureAwait(false); + + bool ok = + string.Equals( + preview.SceneName, + sceneName, + StringComparison.Ordinal + ) + && string.Equals( + restored.SceneName, + originalScene, + StringComparison.Ordinal + ); + + return ( + ok, + $"wanted {sceneName} got {preview.SceneName}, " + + $"then wanted {originalScene} got {restored.SceneName}" + ); + } + finally + { + if (!studio.StudioModeEnabled) + { + await client + .Ui.SetStudioModeEnabledAsync(new(false), cancellationToken) + .ConfigureAwait(false); + } + } + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "SourceExistsAsync", + async () => + { + bool present = await client + .Sources.SourceExistsAsync(inputName, cancellationToken) + .ConfigureAwait(false); + bool absent = await client + .Sources.SourceExistsAsync("__absent__", cancellationToken) + .ConfigureAwait(false); + + return (present && !absent, $"present={present}, absent={absent}"); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "SetInputMutesAsync", + async () => + { + GetInputMuteResponseData before = await client + .Inputs.GetInputMuteAsync( + new(inputName: inputName), + cancellationToken + ) + .ConfigureAwait(false); + + // One real input beside one that does not exist, so the returned + // results have to show a success next to a failure. + BatchResults muteResults = await client + .Inputs.SetInputMutesAsync( + [(inputName, !before.InputMuted), ("__absent__", true)], + cancellationToken + ) + .ConfigureAwait(false); + + GetInputMuteResponseData after = await client + .Inputs.GetInputMuteAsync( + new(inputName: inputName), + cancellationToken + ) + .ConfigureAwait(false); + + await client + .Inputs.SetInputMutesAsync( + [(inputName, before.InputMuted)], + cancellationToken + ) + .ConfigureAwait(false); + + bool ok = + muteResults.Count == 2 + && muteResults[0].RequestStatus.Result + && !muteResults[1].RequestStatus.Result + && after.InputMuted == !before.InputMuted; + + return ( + ok, + $"{muteResults.Count} results, muted {before.InputMuted} -> {after.InputMuted}" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Transition settings read", + async () => + { + GetCurrentSceneTransitionResponseData current = await client + .Transitions.GetCurrentSceneTransitionAsync(cancellationToken) + .ConfigureAwait(false); + // The typed Get*SettingsAsync helpers deserialize into a settings + // record; the generated request is the way to read the raw JSON. + // A transition with nothing to configure, such as Fade, legitimately + // reports no settings, so the name and kind are what is asserted. + JsonElement? settings = current.TransitionSettings; + + return ( + !string.IsNullOrEmpty(current.TransitionName) + && !string.IsNullOrEmpty(current.TransitionKind), + $"{current.TransitionName} ({current.TransitionKind}), " + + $"settings {(settings is null ? "none" : "present")}" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Canvas screenshot helper", + async () => + { + byte[]? bytes = await client + .Sources.GetSourceScreenshotOnCanvasBytesAsync( + sceneName, + "png", + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + bool png = + bytes is { Length: > 8 } + && bytes[0] == 0x89 + && bytes[1] == 0x50 + && bytes[2] == 0x4E + && bytes[3] == 0x47; + + return (png, $"{bytes?.Length ?? 0} bytes at canvas size"); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Parallel batch, verdict without attribution", + async () => + { + // A parallel batch of writes is the case Parallel is actually good + // for: OBS mispairs the rows, but a verdict over all of them does not + // depend on which row is which. + ObsBatchBuilder par = new(); + _ = par.Inputs.SetInputMute( + new SetInputMuteRequestData(inputName: inputName, inputMuted: true) + ); + _ = par.Inputs.SetInputMute( + new SetInputMuteRequestData(inputName: inputName, inputMuted: false) + ); + + BatchResults ok = await client + .CallBatchAsync( + par, + executionType: RequestBatchExecutionType.Parallel, + haltOnFailure: false, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + // Same again with one request that cannot succeed, so the count of + // failures is checked as well as the all-succeeded verdict. + ObsBatchBuilder mixed = new(); + _ = mixed.Inputs.SetInputMute( + new SetInputMuteRequestData(inputName: inputName, inputMuted: false) + ); + _ = mixed.Inputs.SetInputMute( + new SetInputMuteRequestData( + inputName: "__absent__", + inputMuted: true + ) + ); + + BatchResults partial = await client + .CallBatchAsync( + mixed, + executionType: RequestBatchExecutionType.Parallel, + haltOnFailure: false, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + int failures = partial.GetFailures().Count(); + + return ( + ok.AllSucceeded() && !partial.AllSucceeded() && failures == 1, + $"all-ok verdict {ok.AllSucceeded()}, mixed verdict " + + $"{partial.AllSucceeded()} with {failures} failure(s)" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Concurrent requests keep their own results", + async () => + { + // The answer to "how do I run things in parallel" once a parallel + // batch is ruled out for reads. The client multiplexes on request id. + Task version = client.General.GetVersionAsync( + cancellationToken + ); + Task video = + client.Config.GetVideoSettingsAsync(cancellationToken); + Task itemsHere = + client.SceneItems.GetSceneItemListAsync( + new GetSceneItemListRequestData(sceneName: sceneName), + cancellationToken + ); + Task itemsThere = + client.SceneItems.GetSceneItemListAsync( + new GetSceneItemListRequestData(sceneName: originalScene), + cancellationToken + ); + + await Task.WhenAll(version, video, itemsHere, itemsThere) + .ConfigureAwait(false); + + // Each answer has to match what the same request returns on its own. + GetVersionResponseData serialVersion = await client + .General.GetVersionAsync(cancellationToken) + .ConfigureAwait(false); + GetSceneItemListResponseData serialHere = await client + .SceneItems.GetSceneItemListAsync( + new GetSceneItemListRequestData(sceneName: sceneName), + cancellationToken + ) + .ConfigureAwait(false); + + bool ok = + string.Equals( + version.Result.ObsVersion, + serialVersion.ObsVersion, + StringComparison.Ordinal + ) + && video.Result.FpsNumerator > 0 + && itemsHere.Result.SceneItems?.Count + == serialHere.SceneItems?.Count; + + return ( + ok, + $"v={version.Result.ObsVersion}, fps={video.Result.FpsNumerator}, " + + $"items {itemsHere.Result.SceneItems?.Count} here vs " + + $"{itemsThere.Result.SceneItems?.Count} there" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Low level Add and raw results", + async () => + { + // Hand rolled batch: Add covers anything the generated methods do not, + // and the raw payload helpers read it back. + ObsBatchBuilder raw = new(); + _ = raw.Add("GetVersion"); + _ = raw.Add("GetStats"); + + BatchResults rawResults = await client + .CallBatchAsync( + raw, + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + GetVersionResponseData? v = rawResults + .Raw[0] + .GetData(); + + // And the same request without any batch at all. + GetVersionResponseData? direct = await client + .CallAsync( + "GetVersion", + null, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + bool ok = + rawResults.Count == 2 + && v is not null + && direct is not null + && string.Equals( + v.ObsVersion, + direct.ObsVersion, + StringComparison.Ordinal + ); + + return ( + ok, + $"raw batch {rawResults.Count}, CallAsync {direct?.ObsVersion}" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Canvases category", + async () => + { + // The only request in its category, and the one stub type nothing + // else reaches. + GetCanvasListResponseData canvases = await client + .Canvases.GetCanvasListAsync(cancellationToken) + .ConfigureAwait(false); + + return ( + canvases.Canvases.Count > 0, + $"{canvases.Canvases.Count} canvas(es)" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Request with neither payload", + async () => + { + // The one generated shape nothing else exercises: no request data and + // no response. Pointing Preview at the scene already in Program makes + // the transition a no-op, so the check is safe to run. + GetStudioModeEnabledResponseData studio = await client + .Ui.GetStudioModeEnabledAsync(cancellationToken) + .ConfigureAwait(false); + if (!studio.StudioModeEnabled) + { + await client + .Ui.SetStudioModeEnabledAsync(new(true), cancellationToken) + .ConfigureAwait(false); + } + + try + { + GetSceneListResponseData before = await client + .Scenes.GetSceneListAsync(new(), cancellationToken) + .ConfigureAwait(false); + string program = before.CurrentProgramSceneName!; + + // Plain switch, not the waiting variant: OBS raises no + // CurrentPreviewSceneChanged when the preview is already that + // scene, which enabling Studio Mode has just made it. + await client + .Scenes.SwitchPreviewSceneAsync(program, cancellationToken) + .ConfigureAwait(false); + + await client + .Transitions.TriggerStudioModeTransitionAsync(cancellationToken) + .ConfigureAwait(false); + + GetSceneListResponseData after = await client + .Scenes.GetSceneListAsync(new(), cancellationToken) + .ConfigureAwait(false); + + return ( + string.Equals( + after.CurrentProgramSceneName, + program, + StringComparison.Ordinal + ), + $"transitioned, program still '{after.CurrentProgramSceneName}'" + ); + } + finally + { + if (!studio.StudioModeEnabled) + { + await client + .Ui.SetStudioModeEnabledAsync(new(false), cancellationToken) + .ConfigureAwait(false); + } + } + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Nested request object", + async () => + { + // keyModifiers is the one generated nested record, a distinct shape + // from the flat request payloads. F13 is not bound by default, so the + // press does nothing. + await client + .General.TriggerHotkeyByKeySequenceAsync( + new TriggerHotkeyByKeySequenceRequestData( + keyId: "OBS_KEY_F13", + keyModifiers: new TriggerHotkeyByKeySequenceRequestData_KeyModifiers( + shift: false, + control: true, + alt: false, + command: false + ) + ), + cancellationToken + ) + .ConfigureAwait(false); + + return (true, "nested keyModifiers accepted"); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Remaining stub types", + async () => + { + // Monitor, output and transition stubs are generated the same way as + // the scene and input ones, so one read each covers the shape. + GetMonitorListResponseData monitors = await client + .Ui.GetMonitorListAsync(cancellationToken) + .ConfigureAwait(false); + GetSceneTransitionListResponseData transitions = await client + .Transitions.GetSceneTransitionListAsync(cancellationToken) + .ConfigureAwait(false); + GetOutputListResponseData outputs = await client + .Outputs.GetOutputListAsync(cancellationToken) + .ConfigureAwait(false); + + bool ok = + monitors.Monitors.Count > 0 + && transitions.Transitions.Count > 0 + && outputs.Outputs.Count > 0 + && monitors.Monitors[0].MonitorWidth > 0 + && !string.IsNullOrEmpty(transitions.Transitions[0].TransitionName) + && !string.IsNullOrEmpty(outputs.Outputs[0].OutputName); + + return ( + ok, + $"{monitors.Monitors.Count} monitor(s) first {monitors.Monitors[0].MonitorWidth}px, " + + $"{transitions.Transitions.Count} transition(s), {outputs.Outputs.Count} output(s)" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Classic handler and subscriptions", + async () => + { + // The stream path is covered above; this is the += path, plus the + // negotiated subscription flags the client reports. + TaskCompletionSource seen = new( + TaskCreationOptions.RunContinuationsAsynchronously + ); + string? observed = null; + void Handler(object? sender, SceneCreatedEventArgs e) + { + observed = e.EventData.SceneName; + _ = seen.TrySetResult(); + } + + client.Scenes.SceneCreated += Handler; + string probe = $"__obsws_handler_{Guid.NewGuid():N}"[..24]; + try + { + await client + .Scenes.CreateSceneAsync( + new CreateSceneRequestData(sceneName: probe), + cancellationToken + ) + .ConfigureAwait(false); + await seen + .Task.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken) + .ConfigureAwait(false); + } + finally + { + client.Scenes.SceneCreated -= Handler; + await client + .Scenes.RemoveSceneAsync( + new RemoveSceneRequestData(sceneName: probe), + CancellationToken.None + ) + .ConfigureAwait(false); + } + + EventSubscription? subs = client.CurrentEventSubscriptions; + + return ( + string.Equals(observed, probe, StringComparison.Ordinal) + && subs is not null + && subs.Value.HasFlag(EventSubscription.Scenes), + $"handler saw '{observed}', subscriptions {subs}" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Every way of sending a request", + async () => + { + // One request, reached six ways, so no path is left unexercised. + // 1. The generated request on its category group. + GetVersionResponseData viaGroup = await client + .General.GetVersionAsync(cancellationToken) + .ConfigureAwait(false); + + // 2. The low level typed call, for a reference type response. + GetVersionResponseData? viaCall = await client + .CallAsync( + "GetVersion", + null, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + // 3. The low level untyped call, for a value type response. + JsonElement? viaValue = await client + .CallAsyncValue( + "GetVersion", + null, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + string? viaRawJson = viaValue?.GetProperty("obsVersion").GetString(); + + // 4. A hand built JsonElement as the request payload. + using JsonDocument requestBody = JsonDocument.Parse( + $$"""{"sceneName":"{{sceneName}}"}""" + ); + JsonElement? viaJsonBody = await client + .CallAsyncValue( + "GetSceneItemList", + requestBody.RootElement, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + int itemsViaJsonBody = + viaJsonBody?.GetProperty("sceneItems").GetArrayLength() ?? -1; + + // 5. The typed batch builder. + ObsBatchBuilder builder = new(); + BatchRef batched = builder.General.GetVersion(); + BatchResults built = await client + .CallBatchAsync( + builder, + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + string viaBatch = built.Get(batched).ObsVersion; + + // 6. A hand rolled batch item, with a JsonElement payload. + using JsonDocument batchBody = JsonDocument.Parse( + $$"""{"sceneName":"{{sceneName}}"}""" + ); + List> viaRawBatch = await client + .CallBatchAsync( + [ + new BatchRequestItem("GetVersion", null), + new BatchRequestItem( + "GetSceneItemList", + batchBody.RootElement + ), + ], + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + string? viaRawBatchVersion = viaRawBatch[0] + .GetData() + ?.ObsVersion; + + // 7. A consumer's own JsonSerializerContext, so a type this library + // has never heard of is sent without hand building a JsonElement. + JsonElement? viaConsumerContext = await client + .CallAsyncValue( + "GetSceneItemList", + new ConsumerSceneRequest(sceneName), + ExampleRequestContext.Default.ConsumerSceneRequest, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + int itemsViaContext = + viaConsumerContext?.GetProperty("sceneItems").GetArrayLength() + ?? -1; + + // And the one shape that is not supported, asserted as unsupported: + // an anonymous object has no metadata in the serializer context. + string anonymous; + try + { + _ = await client + .CallAsyncValue( + "GetSceneItemList", + new { sceneName }, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + anonymous = "unexpectedly accepted"; + } + catch (ObsWebSocketSerializationException) + { + anonymous = "refused"; + } + + string expected = viaGroup.ObsVersion; + bool allAgree = + viaCall?.ObsVersion == expected + && viaRawJson == expected + && viaBatch == expected + && viaRawBatchVersion == expected + && itemsViaJsonBody >= 0 + && itemsViaContext == itemsViaJsonBody + && anonymous == "refused"; + + return ( + allAgree, + $"seven paths agree on {expected}, JsonElement body and consumer " + + $"context both read {itemsViaJsonBody} item(s), anonymous " + + $"object {anonymous}" + ); + } + ) + .ConfigureAwait(false) + ); + results.Add( await TrySettingsCheckAsync( "Typed exception on a rejected request", @@ -2384,8 +3096,9 @@ await TrySettingsCheckAsync( catch (ObsWebSocketRequestException ex) { return ( - ex.Status?.Code == 600 && ex.RequestType == "GetSceneItemList", - $"{ex.RequestType} code {ex.Status?.Code}" + ex.StatusCode == RequestStatusCode.ResourceNotFound + && ex.RequestType == "GetSceneItemList", + $"{ex.RequestType} code {(int?)ex.StatusCode}" ); } } @@ -3365,9 +4078,7 @@ private void OnInputCreated(object? sender, InputCreatedEventArgs e) => private void OnStreamStateChanged(object? sender, StreamStateChangedEventArgs e) { - // The wire value is a string; OutputStateExtensions.FromWireValue turns it into the - // typed enum so it can be matched instead of compared against protocol constants. - string description = OutputStateExtensions.FromWireValue(e.EventData.OutputState) switch + string description = e.EventData.OutputState switch { OutputState.Starting => "starting up", OutputState.Started => "live", @@ -3377,7 +4088,6 @@ private void OnStreamStateChanged(object? sender, StreamStateChangedEventArgs e) OutputState.Reconnected => "reconnected", OutputState.Paused => "paused", OutputState.Unknown => "in an unknown state", - null => $"reporting an unrecognised state ({e.EventData.OutputState})", _ => "in an unhandled state", }; @@ -3430,3 +4140,19 @@ internal sealed record WorkerGainDbSettings([property: JsonPropertyName("db")] d DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault )] internal sealed partial class WorkerSettingsJsonContext : JsonSerializerContext { } + +/// +/// A request payload this library has no metadata for, sent with the consumer's own context. +/// +/// The scene to list items for. +internal sealed record ConsumerSceneRequest( + [property: System.Text.Json.Serialization.JsonPropertyName("sceneName")] string SceneName +); + +/// +/// The consumer side serializer context, the AOT safe way to describe a payload the library does +/// not model. +/// +[System.Text.Json.Serialization.JsonSerializable(typeof(ConsumerSceneRequest))] +internal sealed partial class ExampleRequestContext + : System.Text.Json.Serialization.JsonSerializerContext; diff --git a/ObsWebSocket.Tests/BatchResultTests.cs b/ObsWebSocket.Tests/BatchResultTests.cs index f0d7685..64e902d 100644 --- a/ObsWebSocket.Tests/BatchResultTests.cs +++ b/ObsWebSocket.Tests/BatchResultTests.cs @@ -47,10 +47,7 @@ private static RequestResponsePayload MsgPackResult(string type, object [TestMethod] public void GetRequiredData_JsonPayload_Deserializes() { - RequestResponsePayload result = JsonResult( - "GetVersion", - new GetVersionResponseData { ObsVersion = "32.2.2", RpcVersion = 1 } - ); + RequestResponsePayload result = JsonResult("GetVersion", TestUtils.SampleVersion()); Assert.AreEqual("32.2.2", result.GetRequiredData().ObsVersion); } @@ -61,7 +58,7 @@ public void GetRequiredData_MsgPackPayload_Deserializes() // The MessagePack transport hands back raw payload bytes rather than a JsonElement. RequestResponsePayload result = MsgPackResult( "GetVersion", - new GetVersionResponseData { ObsVersion = "32.2.2", RpcVersion = 1 } + TestUtils.SampleVersion() ); Assert.AreEqual("32.2.2", result.GetRequiredData().ObsVersion); @@ -90,7 +87,7 @@ public void GetRequiredData_FailedRequest_ThrowsCarryingStatus() result.GetRequiredData() ); - Assert.AreEqual(600, ex.Status?.Code); + Assert.AreEqual(600, (int?)ex.StatusCode); Assert.AreEqual("GetSceneItemList", ex.RequestType); } @@ -113,10 +110,7 @@ public void AllSucceededAndGetFailures_ReflectStatuses() { List> results = [ - JsonResult( - "GetVersion", - new GetVersionResponseData { ObsVersion = "1", RpcVersion = 1 } - ), + JsonResult("GetVersion", TestUtils.SampleVersion("1")), new("GetStats", "GetStats_1", new RequestStatus(false, 604, "nope"), null), ]; diff --git a/ObsWebSocket.Tests/EventStreamTests.cs b/ObsWebSocket.Tests/EventStreamTests.cs index fe9080b..37447cd 100644 --- a/ObsWebSocket.Tests/EventStreamTests.cs +++ b/ObsWebSocket.Tests/EventStreamTests.cs @@ -34,7 +34,10 @@ public void Raise(string sceneName) => Fired?.Invoke( this, new CurrentProgramSceneChangedEventArgs( - new CurrentProgramSceneChangedPayload(sceneName: sceneName) + new CurrentProgramSceneChangedPayload( + sceneName: sceneName, + sceneUuid: Guid.NewGuid().ToString() + ) ) ); } diff --git a/ObsWebSocket.Tests/GroupEventTests.cs b/ObsWebSocket.Tests/GroupEventTests.cs new file mode 100644 index 0000000..52a8b2c --- /dev/null +++ b/ObsWebSocket.Tests/GroupEventTests.cs @@ -0,0 +1,76 @@ +using ObsWebSocket.Core; +using ObsWebSocket.Core.Events.Generated; + +namespace ObsWebSocket.Tests; + +/// +/// The category groups are structs the property hands out fresh on every access, so subscribing +/// through one has to reach the client's own delegate list rather than a temporary. Removal +/// matters most: a remove that silently did nothing would leak every handler. +/// +[TestClass] +public sealed class GroupEventTests +{ + private static Delegate[] Handlers(ObsWebSocketClient client, string eventName) => + TestUtils.GetPrivateField(client, eventName)?.GetInvocationList() ?? []; + + [TestMethod] + public void AddAndRemoveThroughTheGroup_ReachTheClientsList() + { + (ObsWebSocketClient client, _, _) = TestUtils.SetupConnectedClientForceState(); + static void Handler(object? sender, CurrentProgramSceneChangedEventArgs e) { } + + Assert.AreEqual(0, Handlers(client, "CurrentProgramSceneChanged").Length); + + client.Scenes.CurrentProgramSceneChanged += Handler; + Assert.AreEqual( + 1, + Handlers(client, "CurrentProgramSceneChanged").Length, + "adding through the group should reach the client" + ); + + client.Scenes.CurrentProgramSceneChanged -= Handler; + Assert.AreEqual( + 0, + Handlers(client, "CurrentProgramSceneChanged").Length, + "removing through the group must not leave the handler attached" + ); + } + + [TestMethod] + public void TheGroupAndTheClientShareOneSubscriptionList() + { + (ObsWebSocketClient client, _, _) = TestUtils.SetupConnectedClientForceState(); + static void Handler(object? sender, CurrentProgramSceneChangedEventArgs e) { } + + client.CurrentProgramSceneChanged += Handler; + client.Scenes.CurrentProgramSceneChanged -= Handler; + Assert.AreEqual( + 0, + Handlers(client, "CurrentProgramSceneChanged").Length, + "a handler added on the client should be removable through the group" + ); + + client.Scenes.CurrentProgramSceneChanged += Handler; + client.CurrentProgramSceneChanged -= Handler; + Assert.AreEqual( + 0, + Handlers(client, "CurrentProgramSceneChanged").Length, + "a handler added through the group should be removable on the client" + ); + } + + [TestMethod] + public void EveryCategoryGroupCarriesItsEvents() + { + // The point of the change: a caller never has to know that some events sit on the client + // and some on a group. + (ObsWebSocketClient client, _, _) = TestUtils.SetupConnectedClientForceState(); + + Assert.IsNotNull(typeof(ScenesGroup).GetEvent(nameof(client.SceneCreated))); + Assert.IsNotNull(typeof(InputsGroup).GetEvent(nameof(client.InputCreated))); + Assert.IsNotNull(typeof(OutputsGroup).GetEvent(nameof(client.StreamStateChanged))); + Assert.IsNotNull(typeof(SceneItemsGroup).GetEvent(nameof(client.SceneItemCreated))); + Assert.IsNotNull(typeof(UiGroup).GetEvent(nameof(client.StudioModeStateChanged))); + } +} diff --git a/ObsWebSocket.Tests/JsonElementListTests.cs b/ObsWebSocket.Tests/JsonElementListTests.cs new file mode 100644 index 0000000..0207a73 --- /dev/null +++ b/ObsWebSocket.Tests/JsonElementListTests.cs @@ -0,0 +1,63 @@ +using System.Text.Json; +using MessagePack; +using ObsWebSocket.Core.Protocol.Responses; +using ObsWebSocket.Core.Serialization; + +namespace ObsWebSocket.Tests; + +/// +/// An array whose item type the protocol does not state is generated as a list of +/// . Nothing in the MessagePack resolver chain could build a formatter for +/// one, so GetCanvasList could not be read at all on that transport while JSON read it fine. +/// +[TestClass] +public sealed class JsonElementListTests +{ + [TestMethod] + public void MsgPack_RoundTripsAListOfJsonElement() + { + using JsonDocument doc = JsonDocument.Parse( + """{"canvasName":"Main","canvasVideoSettings":{"baseWidth":1920,"fpsNumerator":30}}""" + ); + GetCanvasListResponseData original = new() { Canvases = [doc.RootElement.Clone()] }; + + byte[] packed = MessagePackSerializer.Serialize( + original, + MsgPackMessageSerializer.s_msgPackOptions + ); + GetCanvasListResponseData read = + MessagePackSerializer.Deserialize( + packed, + MsgPackMessageSerializer.s_msgPackOptions + ); + + Assert.AreEqual(1, read.Canvases.Count); + Assert.AreEqual("Main", read.Canvases[0].GetProperty("canvasName").GetString()); + Assert.AreEqual( + 1920, + read.Canvases[0].GetProperty("canvasVideoSettings").GetProperty("baseWidth").GetInt32(), + "a nested object inside the element has to survive too" + ); + } + + [TestMethod] + public void MsgPack_EmptyListRoundTrips() + { + GetCanvasListResponseData original = new() { Canvases = [] }; + + byte[] packed = MessagePackSerializer.Serialize( + original, + MsgPackMessageSerializer.s_msgPackOptions + ); + + Assert.AreEqual( + 0, + MessagePackSerializer + .Deserialize( + packed, + MsgPackMessageSerializer.s_msgPackOptions + ) + .Canvases.Count + ); + } +} diff --git a/ObsWebSocket.Tests/MSTestSettings.cs b/ObsWebSocket.Tests/MSTestSettings.cs index aaf278c..654d907 100644 --- a/ObsWebSocket.Tests/MSTestSettings.cs +++ b/ObsWebSocket.Tests/MSTestSettings.cs @@ -1 +1,6 @@ -[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] +// Method-level parallelism with a fixed worker count. Left to its default, MSTest runs one worker +// per processor, and `dotnet test` runs every target framework at once, so the suite asks for +// several times the machine's parallelism. The tests that suffer are the ones asserting a timeout: +// their continuations wait on a thread pool that is saturated, and they fail on a busy machine +// rather than on a defect. +[assembly: Parallelize(Workers = 4, Scope = ExecutionScope.MethodLevel)] diff --git a/ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs b/ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs index 295b10f..989c819 100644 --- a/ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs +++ b/ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs @@ -714,8 +714,10 @@ await Assert.ThrowsExactlyAsync(() => mockFactory.Verify(f => f.CreateConnection(), Times.Exactly(maxAttempts)); } + // Its own two signal waits total five seconds in the worst case, so the shared six second + // budget left no headroom and the test failed on a busy machine rather than on a defect. [TestMethod] - [Timeout(TestTimeout)] + [Timeout(20_000)] public async Task DisconnectAsync_DuringRetry_StopsRetriesAndDisconnectsGracefully() { // Arrange diff --git a/ObsWebSocket.Tests/ObsWebSocketClientIntegrationTests.cs b/ObsWebSocket.Tests/ObsWebSocketClientIntegrationTests.cs index 3ae7f22..0bfa367 100644 --- a/ObsWebSocket.Tests/ObsWebSocketClientIntegrationTests.cs +++ b/ObsWebSocket.Tests/ObsWebSocketClientIntegrationTests.cs @@ -150,7 +150,6 @@ public async Task ConnectDisconnect_ValidUri_Succeeds() Assert.IsTrue(client.IsConnected, "Client should be connected after ConnectAsync."); Assert.IsTrue(connectedEventFired, "Connected event should have fired."); - Assert.IsNotNull(client.NegotiatedRpcVersion, "NegotiatedRpcVersion should be set."); Assert.IsTrue(client.NegotiatedRpcVersion >= 1, "NegotiatedRpcVersion should be >= 1."); Assert.IsNotNull( client.CurrentEventSubscriptions, @@ -198,7 +197,6 @@ public async Task GetVersion_ReturnsValidData() "ObsWebSocketVersion should not be empty." ); Assert.IsGreaterThanOrEqualTo(version.RpcVersion, 1, "RpcVersion should be >= 1."); - Assert.IsNotNull(version.AvailableRequests, "AvailableRequests should not be null."); Assert.IsNotEmpty(version.AvailableRequests, "AvailableRequests should not be empty."); } @@ -355,7 +353,6 @@ public async Task GetSceneList_ReturnsSceneStubs() GetSceneListResponseData? response = await client.Scenes.GetSceneListAsync(new()); Assert.IsNotNull(response, "GetSceneList response was null."); - Assert.IsNotNull(response.Scenes, "Scenes list was null."); Assert.IsNotEmpty(response.Scenes, "Expected at least one scene in the list."); // Find the test scene using the SceneStub @@ -397,7 +394,6 @@ public async Task GetInputList_ReturnsInputStubs() ); // Empty request data Assert.IsNotNull(response, "GetInputList response was null."); - Assert.IsNotNull(response.Inputs, "Inputs list was null."); Assert.IsNotEmpty(response.Inputs, "Expected at least one input in the list."); // Find the test input @@ -457,16 +453,9 @@ await client.SceneItems.GetSceneItemTransformAsync( ); Assert.IsNotNull(transformResponse, "GetSceneItemTransform response was null."); - Assert.IsNotNull(transformResponse.SceneItemTransform, "SceneItemTransform data was null."); // Validate some core transform properties SceneItemTransformStub transform = transformResponse.SceneItemTransform; - Assert.IsNotNull(transform.PositionX, "PositionX should have a value."); - Assert.IsNotNull(transform.PositionY, "PositionY should have a value."); - Assert.IsNotNull(transform.ScaleX, "ScaleX should have a value."); - Assert.IsNotNull(transform.ScaleY, "ScaleY should have a value."); - Assert.IsNotNull(transform.Width, "Width should have a value."); - Assert.IsNotNull(transform.Height, "Height should have a value."); Trace.WriteLine( $"Transform for Item {sceneItemId}: Pos=({transform.PositionX},{transform.PositionY}), Scale=({transform.ScaleX},{transform.ScaleY}), Size=({transform.Width}x{transform.Height})" ); @@ -497,7 +486,7 @@ public async Task GetInputAudioTracks_ReturnsDictionary() } Assert.IsNotNull(response, "GetInputAudioTracks response was null."); - Assert.IsNotNull(response.InputAudioTracks, "InputAudioTracks dictionary was null."); + Assert.IsNotNull(response.InputAudioTracks, "Audio tracks object was null."); Assert.IsNotEmpty(response.InputAudioTracks, "Expected at least one audio track."); // Check if common tracks exist (OBS usually has 6) @@ -549,7 +538,6 @@ public async Task GetInputSettings_TextGDI_ReturnsJsonElement() } Assert.IsNotNull(response, "GetInputSettings response was null."); - Assert.IsNotNull(response.InputSettings, "InputSettings (JsonElement?) was null."); Assert.AreEqual( "text_gdiplus_v3", response.InputKind, @@ -557,6 +545,7 @@ public async Task GetInputSettings_TextGDI_ReturnsJsonElement() ); // Verify kind // Demonstrate deserializing the JsonElement + Assert.IsNotNull(response.InputSettings, "Input settings were null."); JsonElement settingsElement = response.InputSettings.Value; Assert.AreEqual( JsonValueKind.Object, @@ -569,10 +558,8 @@ public async Task GetInputSettings_TextGDI_ReturnsJsonElement() TestUtils.s_jsonSerializerOptions ); Assert.IsNotNull(textSettings, "Failed to deserialize settings element."); - Assert.IsNotNull(textSettings.Text, "Expected 'text' property in settings."); Trace.WriteLine($"Text GDI+ Settings 'text' property: {textSettings.Text}"); - Assert.IsNotNull(textSettings.Font, "Expected 'font' property in settings."); - Trace.WriteLine($"Text GDI+ Settings 'font.face': {textSettings.Font.Face}"); + Trace.WriteLine($"Text GDI+ Settings 'font.face': {textSettings.Font?.Face}"); } [TestMethod, TestCategory("Integration")] @@ -600,7 +587,6 @@ public async Task GetSourceFilterList_ReturnsFilterStubs() } Assert.IsNotNull(response, "GetSourceFilterList response was null."); - Assert.IsNotNull(response.Filters, "Filters list was null."); FilterStub? testFilter = response.Filters.FirstOrDefault(f => f.FilterName == s_testOptions.TestFilterName @@ -613,8 +599,6 @@ public async Task GetSourceFilterList_ReturnsFilterStubs() string.IsNullOrWhiteSpace(testFilter.FilterKind), "Filter kind should not be empty." ); - Assert.IsNotNull(testFilter.FilterIndex, "Filter index should have a value."); - Assert.IsNotNull(testFilter.FilterEnabled, "Filter enabled should have a value."); Trace.WriteLine( $"Found Test Filter Stub: Name={testFilter.FilterName}, Kind={testFilter.FilterKind}, Index={testFilter.FilterIndex}, Enabled={testFilter.FilterEnabled}" ); @@ -644,7 +628,6 @@ public async Task GetTransitionList_ReturnsTransitionStubs() await client.Transitions.GetSceneTransitionListAsync(); Assert.IsNotNull(response, "GetSceneTransitionList response was null."); - Assert.IsNotNull(response.Transitions, "Transitions list was null."); Assert.IsNotEmpty(response.Transitions, "Expected at least one transition."); Assert.IsFalse( string.IsNullOrWhiteSpace(response.CurrentSceneTransitionName), @@ -661,11 +644,6 @@ public async Task GetTransitionList_ReturnsTransitionStubs() string.IsNullOrWhiteSpace(firstTransition.TransitionKind), "Transition kind should not be empty." ); - Assert.IsNotNull( - firstTransition.TransitionConfigurable, - "Transition configurable flag should exist." - ); - Assert.IsNotNull(firstTransition.TransitionFixed, "Transition fixed flag should exist."); Trace.WriteLine($"Current Transition: {response.CurrentSceneTransitionName}"); Trace.WriteLine( $"First Transition Stub: Name={firstTransition.TransitionName}, Kind={firstTransition.TransitionKind}" @@ -684,7 +662,6 @@ public async Task GetOutputList_ReturnsOutputStubs() GetOutputListResponseData? response = await client.Outputs.GetOutputListAsync(); Assert.IsNotNull(response, "GetOutputList response was null."); - Assert.IsNotNull(response.Outputs, "Outputs list was null."); Assert.IsNotEmpty( response.Outputs, "Expected at least one output (e.g., Simple Output or Advanced)." @@ -700,7 +677,6 @@ public async Task GetOutputList_ReturnsOutputStubs() string.IsNullOrWhiteSpace(firstOutput.OutputKind), "Output kind should not be empty." ); - Assert.IsNotNull(firstOutput.OutputActive, "Output active flag should exist."); // Note: Width/Height/Settings might be null depending on the output type and state Trace.WriteLine( $"First Output Stub: Name={firstOutput.OutputName}, Kind={firstOutput.OutputKind}, Active={firstOutput.OutputActive}" @@ -719,7 +695,6 @@ public async Task GetMonitorList_ReturnsMonitorStubs() GetMonitorListResponseData? response = await client.Ui.GetMonitorListAsync(); Assert.IsNotNull(response, "GetMonitorList response was null."); - Assert.IsNotNull(response.Monitors, "Monitors list was null."); // Cannot assert count > 0 as user might have no monitors, but list should exist. if (response.Monitors.Count > 0) @@ -730,11 +705,6 @@ public async Task GetMonitorList_ReturnsMonitorStubs() string.IsNullOrWhiteSpace(firstMonitor.MonitorName), "Monitor name should not be empty." ); - Assert.IsNotNull(firstMonitor.MonitorIndex, "Monitor index should exist."); - Assert.IsNotNull(firstMonitor.MonitorWidth, "Monitor width should exist."); - Assert.IsNotNull(firstMonitor.MonitorHeight, "Monitor height should exist."); - Assert.IsNotNull(firstMonitor.MonitorPositionX, "Monitor position X should exist."); - Assert.IsNotNull(firstMonitor.MonitorPositionY, "Monitor position Y should exist."); Trace.WriteLine( $"First Monitor Stub: Name={firstMonitor.MonitorName}, Index={firstMonitor.MonitorIndex}, Res=({firstMonitor.MonitorWidth}x{firstMonitor.MonitorHeight})" ); @@ -838,11 +808,8 @@ public async Task GetInputDefaultSettings_ReturnsJsonElement_CanDeserialize() } Assert.IsNotNull(response, "GetInputDefaultSettings response was null."); - Assert.IsNotNull( - response.DefaultInputSettings, - "DefaultInputSettings (JsonElement?) was null." - ); + Assert.IsNotNull(response.DefaultInputSettings, "Default input settings were null."); JsonElement settingsElement = response.DefaultInputSettings.Value; Assert.AreEqual( JsonValueKind.Object, diff --git a/ObsWebSocket.Tests/ObsWebSocketClientTests.cs b/ObsWebSocket.Tests/ObsWebSocketClientTests.cs index b7a90a0..5c75bd9 100644 --- a/ObsWebSocket.Tests/ObsWebSocketClientTests.cs +++ b/ObsWebSocket.Tests/ObsWebSocketClientTests.cs @@ -149,7 +149,7 @@ public async Task CallBatchAsync_ValidRequest_SendsCorrectBatchMessage() List requests = [request1, request2]; // Expected Response Payloads (for simulation) - GetVersionResponseData response1Data = new(1, "v1", "v5", [], [], "windows", "windows 11"); + GetVersionResponseData response1Data = new("v1", "v5", 1, [], [], "windows", "windows 11"); RequestResponsePayload response1 = new( "GetVersion", "batch1_0", diff --git a/ObsWebSocket.Tests/PayloadShapeTests.cs b/ObsWebSocket.Tests/PayloadShapeTests.cs new file mode 100644 index 0000000..1c9a328 --- /dev/null +++ b/ObsWebSocket.Tests/PayloadShapeTests.cs @@ -0,0 +1,77 @@ +using System.Text.Json; +using MessagePack; +using ObsWebSocket.Core; +using ObsWebSocket.Core.Protocol; +using ObsWebSocket.Core.Protocol.Responses; +using ObsWebSocket.Core.Serialization; + +namespace ObsWebSocket.Tests; + +/// +/// Reading a batch payload as the wrong response record used to be silent on MessagePack, which +/// maps by key name and leaves everything unmatched at its default, so a fabricated reading of +/// cpu=0 looked genuine. JSON was silent too for the response records with no required member, +/// which is most of them. +/// +[TestClass] +public sealed class PayloadShapeTests +{ + private static RequestResponsePayload Row(object payload) => + new("GetVersion", "id", new RequestStatus(true, 100), payload); + + [TestMethod] + public void MsgPack_ReadingAPayloadAsTheWrongRecord_Throws() + { + GetVersionResponseData version = TestUtils.SampleVersion(); + byte[] packed = MessagePackSerializer.Serialize( + version, + MsgPackMessageSerializer.s_msgPackOptions + ); + + RequestResponsePayload row = Row(new ReadOnlyMemory(packed)); + + _ = Assert.ThrowsExactly(() => + row.GetData() + ); + } + + [TestMethod] + public void Json_ReadingAPayloadAsTheWrongRecord_Throws() + { + // GetSceneListResponseData has no required member, so System.Text.Json alone accepted this. + using JsonDocument doc = JsonDocument.Parse("""{"cpuUsage":0.5,"memoryUsage":300.0}"""); + RequestResponsePayload row = Row(doc.RootElement.Clone()); + + _ = Assert.ThrowsExactly(() => + row.GetData() + ); + } + + [TestMethod] + public void ReadingAPayloadAsItsOwnRecord_StillWorks() + { + GetVersionResponseData version = TestUtils.SampleVersion(); + byte[] packed = MessagePackSerializer.Serialize( + version, + MsgPackMessageSerializer.s_msgPackOptions + ); + + GetVersionResponseData? read = Row(new ReadOnlyMemory(packed)) + .GetData(); + + Assert.AreEqual("32.2.2", read?.ObsVersion); + } + + [TestMethod] + public void RecordsWithAnIdenticalShape_AreStillInterchangeable() + { + // GetInputMute and ToggleInputMute are both a single inputMuted field, so reading one as + // the other gives the right value. The check must not reject that. + using JsonDocument doc = JsonDocument.Parse("""{"inputMuted":true}"""); + RequestResponsePayload row = Row(doc.RootElement.Clone()); + + ToggleInputMuteResponseData? read = row.GetData(); + + Assert.IsTrue(read?.InputMuted); + } +} diff --git a/ObsWebSocket.Tests/ReadmeCompileCheck.cs b/ObsWebSocket.Tests/ReadmeCompileCheck.cs index 5906d2d..bec11b9 100644 --- a/ObsWebSocket.Tests/ReadmeCompileCheck.cs +++ b/ObsWebSocket.Tests/ReadmeCompileCheck.cs @@ -11,8 +11,13 @@ namespace ObsWebSocket.Tests; [JsonSerializable(typeof(OverlaySettings))] +[JsonSerializable(typeof(MyRequest))] internal partial class MyContext : JsonSerializerContext { } +/// A request payload the library does not model, described by the consumer's context. +/// An arbitrary field. +internal sealed record MyRequest([property: JsonPropertyName("someField")] int SomeField); + internal sealed record OverlaySettings( [property: JsonPropertyName("url")] string? Url = null, [property: JsonPropertyName("css")] string? Css = null @@ -164,7 +169,7 @@ var e in client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct) break; } - client.CurrentProgramSceneChanged += (_, e) => _ = e.EventData.SceneName; + client.Scenes.CurrentProgramSceneChanged += (_, e) => _ = e.EventData.SceneName; _ = await client.WaitForEventAsync(ct); _ = await client.WaitForEventAsync( @@ -208,14 +213,14 @@ internal static async Task TypedBatchAsync(ObsWebSocketClient client, Cancellati internal static void TypedEnums(ObsWebSocketClient client) { - client.StreamStateChanged += (_, e) => + client.Outputs.StreamStateChanged += (_, e) => { - string what = OutputStateExtensions.FromWireValue(e.EventData.OutputState) switch + string what = e.EventData.OutputState switch { OutputState.Started => "live", OutputState.Starting or OutputState.Reconnecting => "coming up", OutputState.Stopped or OutputState.Stopping => "going down", - null => $"unrecognised ({e.EventData.OutputState})", + OutputState.Unknown => "in an unrecognised state", _ => "in between", }; @@ -268,7 +273,7 @@ internal static async Task TypedErrorsAsync(ObsWebSocketClient client, Cancellat } catch (ObsWebSocketRequestException ex) { - _ = $"{ex.RequestType} failed with {ex.Status?.Code}: {ex.Comment}"; + _ = $"{ex.RequestType} failed with {(int?)ex.StatusCode}: {ex.Comment}"; } catch (ObsWebSocketTimeoutException) { @@ -307,11 +312,178 @@ await client.SceneItems.SetSceneItemIndexAsync( _ = $"{bytes} {volume}"; } + internal static async Task DroppingToTheWireAsync( + ObsWebSocketClient client, + CancellationToken ct + ) + { + string wire = MediaInputAction.Restart.ToWireValue(); + + // Request data has to be a JsonElement or a type the serializer context knows. An + // anonymous object compiles and then throws at runtime, so the README does not use one. + using System.Text.Json.JsonDocument body = System.Text.Json.JsonDocument.Parse( + """{"someField":1}""" + ); + System.Text.Json.JsonElement? raw = + await client.CallAsyncValue( + "SomeNewRequest", + body.RootElement, + cancellationToken: ct + ); + _ = $"{wire} {raw}"; + } + + internal static async Task ParallelRecoveryAsync( + ObsWebSocketClient client, + ObsBatchBuilder batch, + CancellationToken ct + ) + { + BatchResults results = await client.CallBatchAsync( + batch, + executionType: RequestBatchExecutionType.Parallel, + haltOnFailure: false, + cancellationToken: ct + ); + + foreach (RequestResponsePayload row in results.Raw) + { + if (!row.RequestStatus.Result) + { + _ = $"one request failed with {row.RequestStatus.Code}"; + continue; + } + + GetSceneItemListResponseData? data = row.GetData(); + _ = data?.SceneItems?.Count; + } + + _ = results.AllSucceeded(); + _ = results.GetFailures().Count(); + } + + internal static async Task ConcurrentRequestsAsync( + ObsWebSocketClient client, + string[] sceneNames, + CancellationToken ct + ) + { + Task version = client.General.GetVersionAsync(ct); + Task stats = client.General.GetStatsAsync(ct); + Task[] perScene = + [ + .. sceneNames.Select(n => + client.SceneItems.GetSceneItemListAsync(new(sceneName: n), ct) + ), + ]; + + await Task.WhenAll([version, stats, .. perScene.Cast()]); + _ = version.Result.ObsVersion; + } + + internal static async Task LowLevelAsync(ObsWebSocketClient client, CancellationToken ct) + { + GetVersionResponseData? v = await client.CallAsync( + "GetVersion", + null, + cancellationToken: ct + ); + + // Request data has to be a JsonElement or a type the serializer context knows. An + // anonymous object compiles and then throws at runtime, so the README does not use one. + using System.Text.Json.JsonDocument body = System.Text.Json.JsonDocument.Parse( + """{"someField":1}""" + ); + System.Text.Json.JsonElement? raw = + await client.CallAsyncValue( + "SomeNewRequest", + body.RootElement, + cancellationToken: ct + ); + + List> results = await client.CallBatchAsync( + [new BatchRequestItem("GetVersion", null), new BatchRequestItem("GetStats", null)], + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: ct + ); + + foreach (RequestResponsePayload result in results) + { + _ = result.GetData(); + } + + _ = $"{v?.ObsVersion} {raw}"; + } + + internal static async Task GroupedSurfaceAsync(ObsWebSocketClient client, CancellationToken ct) + { + await client.Scenes.GetSceneListAsync(new(), ct); + await client.Scenes.SwitchProgramSceneAndWaitAsync("Intro", cancellationToken: ct); + await client.Inputs.SetInputVolumeDbAsync("Mic", -6, ct); + await client.SceneItems.SetSceneItemEnabledAsync("Intro", "Logo", false, ct); + + client.Scenes.CurrentProgramSceneChanged += (_, e) => { }; + await foreach ( + var e in client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct) + ) + { + break; + } + } + + internal static async Task TimeoutTypeAsync(ObsWebSocketClient client, CancellationToken ct) + { + try + { + _ = await client.WaitForEventAsync( + TimeSpan.FromSeconds(5), + ct + ); + } + catch (ObsWebSocketTimeoutException) + { + // One catch covers a request timeout and a wait timeout alike. + } + } + + internal static async Task ConsumerContextPayloadAsync( + ObsWebSocketClient client, + CancellationToken ct + ) + { + // The preferred way to send a payload the library does not model: your own type and your + // own context, which is AOT safe and needs nothing hand built. + System.Text.Json.JsonElement? answer = + await client.CallAsyncValue( + "SomeNewRequest", + new MyRequest(1), + MyContext.Default.MyRequest, + cancellationToken: ct + ); + + // The alternative, for a one-off payload that does not deserve a type. + using System.Text.Json.JsonDocument body = System.Text.Json.JsonDocument.Parse( + """{"someField":1}""" + ); + System.Text.Json.JsonElement? viaElement = + await client.CallAsyncValue( + "SomeNewRequest", + body.RootElement, + cancellationToken: ct + ); + + _ = $"{answer} {viaElement}"; + } + internal static void HostIntegration( Microsoft.Extensions.Hosting.IHostApplicationBuilder builder ) { - _ = builder.AddObsWebSocketClient("obs").WithAutoConnect().WithHealthCheck(); + _ = builder + .AddObsWebSocketClient("obs") + .WithAutoConnect() + .WithHealthCheck() + .WithReconnectPipeline(); } internal static void TelemetryAndKeyedRegistration(IServiceCollection services) @@ -358,7 +530,7 @@ internal static async Task StudioModeAsync(ObsWebSocketClient client, Cancellati } catch (ObsWebSocketRequestException ex) { - _ = $"{ex.RequestType} failed with {ex.Status?.Code}: {ex.Comment}"; + _ = $"{ex.RequestType} failed with {(int?)ex.StatusCode}: {ex.Comment}"; } catch (ObsWebSocketTimeoutException) { } } diff --git a/ObsWebSocket.Tests/SerializerBehaviorTests.cs b/ObsWebSocket.Tests/SerializerBehaviorTests.cs index 10a67f4..2bdd592 100644 --- a/ObsWebSocket.Tests/SerializerBehaviorTests.cs +++ b/ObsWebSocket.Tests/SerializerBehaviorTests.cs @@ -122,7 +122,7 @@ public void MsgPackSerializer_DeserializePayload_WithComplexFilterBytes_Deserial Assert.AreEqual("Color Correction", filters[0].FilterName); Assert.AreEqual("color_filter_v2", filters[0].FilterKind); Assert.AreEqual(0, filters[0].FilterIndex); - Assert.IsTrue(filters[0].FilterEnabled ?? false); + Assert.IsTrue(filters[0].FilterEnabled); Assert.IsTrue(filters[0].FilterSettings.HasValue); JsonElement filterSettings = filters[0].FilterSettings.GetValueOrDefault(); Assert.AreEqual(0.8d, filterSettings.GetProperty("opacity").GetDouble(), 0.0001d); @@ -387,8 +387,8 @@ public void MsgPackSerializer_DeserializePayload_SceneItemList_WithTransformAndE Assert.AreEqual("Camera", sceneItems[0].SourceName); Core.Protocol.Common.SceneItemTransformStub? transform = sceneItems[0].SceneItemTransform; Assert.IsNotNull(transform); - Assert.IsTrue(transform.Width.HasValue); - Assert.AreEqual(1920d, transform.Width.Value, 0.001d); + Assert.AreNotEqual(0, transform.Width); + Assert.AreEqual(1920d, transform.Width, 0.001d); Dictionary? extensionData = sceneItems[0].ExtensionData; Assert.IsNotNull(extensionData); Assert.AreEqual("group-A", extensionData["customGroup"].GetString()); @@ -448,7 +448,7 @@ public void MsgPackSerializer_SerializeThenDeserialize_FilterPayload_RoundTripsW Assert.AreEqual("Limiter", roundTrip.Filters[1].FilterName); Assert.AreEqual("limiter_filter_v2", roundTrip.Filters[1].FilterKind); Assert.AreEqual(1, roundTrip.Filters[1].FilterIndex); - Assert.IsFalse(roundTrip.Filters[1].FilterEnabled ?? true); + Assert.IsFalse(roundTrip.Filters[1].FilterEnabled); Assert.IsFalse(roundTrip.Filters[1].FilterSettings.HasValue); } @@ -592,6 +592,53 @@ private static byte[] BuildTriggerHotkeyNestedPayloadBytes() return [.. buffer.WrittenSpan]; } + /// + /// Writes a scene item transform with every field OBS sends. The stub names them required, so + /// a partial map is no longer a payload OBS would produce. + /// + private static void WriteTransform(ref MessagePackWriter writer, double width, double height) + { + writer.WriteMapHeader(19); + writer.Write("alignment"); + writer.Write(5); + writer.Write("boundsAlignment"); + writer.Write(0); + writer.Write("boundsHeight"); + writer.Write(0d); + writer.Write("boundsType"); + writer.Write("OBS_BOUNDS_NONE"); + writer.Write("boundsWidth"); + writer.Write(0d); + writer.Write("cropBottom"); + writer.Write(0); + writer.Write("cropLeft"); + writer.Write(0); + writer.Write("cropRight"); + writer.Write(0); + writer.Write("cropToBounds"); + writer.Write(false); + writer.Write("cropTop"); + writer.Write(0); + writer.Write("height"); + writer.Write(height); + writer.Write("positionX"); + writer.Write(0d); + writer.Write("positionY"); + writer.Write(0d); + writer.Write("rotation"); + writer.Write(0d); + writer.Write("scaleX"); + writer.Write(1d); + writer.Write("scaleY"); + writer.Write(1d); + writer.Write("sourceHeight"); + writer.Write(height); + writer.Write("sourceWidth"); + writer.Write(width); + writer.Write("width"); + writer.Write(width); + } + private static byte[] BuildSceneItemListPayloadBytes() { ArrayBufferWriter buffer = new(); @@ -600,19 +647,21 @@ private static byte[] BuildSceneItemListPayloadBytes() writer.WriteMapHeader(1); writer.Write("sceneItems"); writer.WriteArrayHeader(1); - writer.WriteMapHeader(6); + writer.WriteMapHeader(9); writer.Write("sceneItemId"); writer.Write(42); + writer.Write("sceneItemIndex"); + writer.Write(0); writer.Write("sourceName"); writer.Write("Camera"); + writer.Write("sourceUuid"); + writer.Write("11111111-2222-3333-4444-555555555555"); writer.Write("sceneItemEnabled"); writer.Write(true); + writer.Write("sceneItemLocked"); + writer.Write(false); writer.Write("sceneItemTransform"); - writer.WriteMapHeader(2); - writer.Write("width"); - writer.Write(1920d); - writer.Write("height"); - writer.Write(1080d); + WriteTransform(ref writer, 1920d, 1080d); writer.Write("customGroup"); writer.Write("group-A"); writer.Write("customArray"); diff --git a/ObsWebSocket.Tests/TestUtils.cs b/ObsWebSocket.Tests/TestUtils.cs index 9e772cc..f0c18f3 100644 --- a/ObsWebSocket.Tests/TestUtils.cs +++ b/ObsWebSocket.Tests/TestUtils.cs @@ -228,6 +228,25 @@ Mock mockConnection /// /// Invokes the private ProcessIncomingMessage method on the client instance using reflection. /// + /// + /// A fully populated GetVersion response. Response records name every field OBS always sends + /// as required, so a test that only cares about one of them still has to supply the rest. + /// + /// The version to report. + internal static ObsWebSocket.Core.Protocol.Responses.GetVersionResponseData SampleVersion( + string obsVersion = "32.2.2" + ) => + new() + { + ObsVersion = obsVersion, + ObsWebSocketVersion = "5.7.0", + RpcVersion = 1, + AvailableRequests = [], + SupportedImageFormats = [], + Platform = "windows", + PlatformDescription = "Windows 11", + }; + internal static void InvokeProcessIncomingMessage( ObsWebSocketClient client, object messageObject diff --git a/ObsWebSocket.Tests/TypedSettingsTests.cs b/ObsWebSocket.Tests/TypedSettingsTests.cs index c115a18..fd3c6bf 100644 --- a/ObsWebSocket.Tests/TypedSettingsTests.cs +++ b/ObsWebSocket.Tests/TypedSettingsTests.cs @@ -831,7 +831,10 @@ Mock mockConnection BrowserSourceSettings settings = new(Url: "https://create.test", Width: 800, Height: 600); JsonElement? capturedSettings = null; - CreateInputResponseData responseDto = new(sceneItemId: 42); + CreateInputResponseData responseDto = new( + inputUuid: Guid.NewGuid().ToString(), + sceneItemId: 42 + ); _ = mockConnection .Setup(ws => @@ -913,7 +916,10 @@ Mock mockConnection .Default .TestConsumerSettings; JsonElement? capturedSettings = null; - CreateInputResponseData responseDto = new(sceneItemId: 7); + CreateInputResponseData responseDto = new( + inputUuid: Guid.NewGuid().ToString(), + sceneItemId: 7 + ); _ = mockConnection .Setup(ws => diff --git a/ObsWebSocket.Tests/WireEnumTests.cs b/ObsWebSocket.Tests/WireEnumTests.cs new file mode 100644 index 0000000..31b0bee --- /dev/null +++ b/ObsWebSocket.Tests/WireEnumTests.cs @@ -0,0 +1,98 @@ +using System.Text.Json; +using MessagePack; +using ObsWebSocket.Core.Protocol.Events; +using ObsWebSocket.Core.Protocol.Generated; +using ObsWebSocket.Core.Protocol.Requests; +using ObsWebSocket.Core.Serialization; + +namespace ObsWebSocket.Tests; + +/// +/// Protocol enums that travel as strings are generated as C# enums, so the value on the wire has +/// to stay the protocol string on both transports. MessagePack would otherwise write the ordinal, +/// which OBS rejects, and which no round trip through this library alone would notice. +/// +[TestClass] +public sealed class WireEnumTests +{ + [TestMethod] + public void Json_WritesTheProtocolStringNotTheMemberName() + { + TriggerMediaInputActionRequestData request = new( + mediaAction: MediaInputAction.Stop, + inputName: "Stinger" + ); + + string json = JsonSerializer.Serialize( + request, + ObsWebSocketJsonContext.Default.TriggerMediaInputActionRequestData + ); + + StringAssert.Contains(json, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP"); + Assert.IsFalse(json.Contains("\"Stop\"", StringComparison.Ordinal), "member name leaked"); + } + + [TestMethod] + public void MsgPack_WritesTheProtocolStringNotTheOrdinal() + { + TriggerMediaInputActionRequestData request = new( + mediaAction: MediaInputAction.Stop, + inputName: "Stinger" + ); + + byte[] bytes = MessagePackSerializer.Serialize( + request, + MsgPackMessageSerializer.s_msgPackOptions + ); + + // Converting to JSON shows what actually landed in the buffer. + string asJson = MessagePackSerializer.ConvertToJson( + bytes, + MsgPackMessageSerializer.s_msgPackOptions + ); + + StringAssert.Contains(asJson, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP"); + } + + [TestMethod] + public void MsgPack_ReadsTheProtocolStringBackIntoTheEnum() + { + StreamStateChangedPayload payload = new( + outputActive: true, + outputState: OutputState.Reconnecting + ); + + byte[] bytes = MessagePackSerializer.Serialize( + payload, + MsgPackMessageSerializer.s_msgPackOptions + ); + StringAssert.Contains( + MessagePackSerializer.ConvertToJson(bytes, MsgPackMessageSerializer.s_msgPackOptions), + "OBS_WEBSOCKET_OUTPUT_RECONNECTING" + ); + + StreamStateChangedPayload read = + MessagePackSerializer.Deserialize( + bytes, + MsgPackMessageSerializer.s_msgPackOptions + ); + + Assert.AreEqual(OutputState.Reconnecting, read.OutputState); + } + + [TestMethod] + public void UnrecognisedWireValue_FallsBackToTheZeroMemberRatherThanThrowing() + { + // A state added by a newer OBS must not fail the whole message. + string json = """{"outputActive":true,"outputState":"OBS_WEBSOCKET_OUTPUT_FUTURE_STATE"}"""; + + StreamStateChangedPayload? read = JsonSerializer.Deserialize( + json, + ObsWebSocketJsonContext.Default.StreamStateChangedPayload + ); + + Assert.IsNotNull(read); + Assert.AreEqual(OutputState.Unknown, read.OutputState); + Assert.IsTrue(read.OutputActive); + } +} diff --git a/README.md b/README.md index 964fc22..1b35124 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,11 @@ reach anything: ```csharp await client.Scenes.GetSceneListAsync(new(), ct); // generated request await client.Scenes.SwitchProgramSceneAndWaitAsync("Intro", cancellationToken: ct); // convenience -await client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct); // event stream await client.Inputs.SetInputVolumeDbAsync("Mic", -6, ct); await client.SceneItems.SetSceneItemEnabledAsync("Intro", "Logo", false, ct); + +client.Scenes.CurrentProgramSceneChanged += (_, e) => { }; // classic event +await foreach (var e in client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct)) { break; } ``` The groups are `Canvases`, `Config`, `Filters`, `General`, `Inputs`, `MediaInputs`, `Outputs`, @@ -88,6 +90,75 @@ protocol definition, so a refresh that recategorises a request moves it here too `WaitForEventAsync` and `CallBatchAsync` stay directly on the client, since neither belongs to one category. +## The helper set + +Alongside the generated request per protocol request, each group carries conveniences for things +that otherwise take several calls or a lookup. Every typed settings helper has two overloads: an +implicit one for library-registered types, and an explicit one taking a `JsonTypeInfo` for +consumer-provided types. Use the explicit overload to stay AOT-safe. + +**Settings read and write** + +| Helper | Notes | +|---|---| +| `Inputs.GetInputSettingsAsync` / `SetInputSettingsAsync` | Input settings; Set supports `overlay` | +| `Inputs.GetInputDefaultSettingsAsync` | Defaults for a given input kind | +| `Filters.GetSourceFilterSettingsAsync` / `SetSourceFilterSettingsAsync` | Filter settings; Set supports `overlay` | +| `Filters.GetSourceFilterDefaultSettingsAsync` | Defaults for a given filter kind | +| `Transitions.GetCurrentSceneTransitionSettingsAsync` / `SetCurrentSceneTransitionSettingsAsync` | Transition settings | +| `Outputs.GetOutputSettingsAsync` / `SetOutputSettingsAsync` | Output settings | +| `Config.GetStreamServiceSettingsAsync` / `SetStreamServiceSettingsAsync` | Stream service settings | + +Most take optional parameters ahead of the cancellation token, so pass it as `cancellationToken: ct`. + +**Scenes and scene items** + +- `Scenes.SwitchProgramSceneAsync(scene, ct)` and `Scenes.SwitchPreviewSceneAsync(scene, ct)` switch + a scene. Optional `transitionName` and `transitionDurationMs` apply to that switch only. +- `Scenes.SwitchProgramSceneAndWaitAsync` and `Scenes.SwitchPreviewSceneAndWaitAsync` do the same + and wait for the event confirming it. +- `SceneItems.SetSceneItemEnabledAsync(scene, sourceName, isEnabled, ct)` returns the resulting + state. Leave `isEnabled` null to toggle. An overload takes the numeric item id instead. +- `SceneItems.FindSceneItemIdAsync(scene, sourceName, ct)` returns `int?`, null rather than throwing + when the item is not in the scene. +- `Sources.SourceExistsAsync(name, ct)` and `Scenes.SceneExistsAsync(name, ct)` check existence. + +**Inputs and filters** + +- `Inputs.SetInputTextAsync(name, text, ct)` is shorthand for updating text source content. +- `Inputs.SetInputVolumeDbAsync(name, db, ct)` and `Inputs.SetInputVolumeMulAsync(name, mul, ct)` + each pick one unit. The underlying request accepts either and fails when given neither. +- `Inputs.SetInputMutesAsync(inputMutes, ct)` sets many mute states in one batch and returns the + results, so a caller sees which inputs OBS rejected. +- `Inputs.CreateInputAsync(kind, name, settings, ...)` creates an input with typed settings. +- `Filters.CreateSourceFilterAsync(source, filterName, kind, settings, ct)` adds a typed filter. + +**Media** + +- `MediaInputs.PlayMediaAsync`, `PauseMediaAsync`, `StopMediaAsync` and `RestartMediaAsync` are + shorthands over `TriggerMediaActionAsync(name, MediaInputAction, ct)`. + +**Screenshots** + +- `Sources.GetSourceScreenshotBytesAsync(source, ...)` returns decoded image bytes. +- `Sources.GetSourceScreenshotOnCanvasBytesAsync(source, ...)` does the same at canvas dimensions. +- `Sources.SaveSourceScreenshotToFileAsync(source, filePath, ...)` writes straight to disk. + +**Outputs** + +- `Record.SetRecordActiveAndWaitAsync(activate, timeout, ct)`, + `Stream.SetStreamActiveAndWaitAsync(...)` and `Outputs.SetVirtualCamActiveAndWaitAsync(...)` start + or stop the output and wait for OBS to confirm, returning the resulting `OutputState`. +- `Record.IsRecordActiveAsync(ct)`, `Stream.IsStreamActiveAsync(ct)` and + `Outputs.IsVirtualCamActiveAsync(ct)` read current state. + +**Application state** + +- `Config.EnsureProfileActiveAsync(name, ct)` and `Config.EnsureSceneCollectionActiveAsync(name, ct)` + switch only if needed, returning whether the target is active rather than throwing when it does + not exist. +- `General.TriggerHotkeyAsync(hotkeyName, ct)` fires a hotkey by name. + ## Observing events Every OBS event is exposed as an async sequence on its category group. The stream subscribes for the @@ -103,13 +174,21 @@ await foreach (var e in client.Scenes.CurrentProgramSceneChangedStream(cancellat Streams buffer a bounded number of events and drop the oldest when a consumer falls behind, so a slow loop cannot stall the receive loop. Pass `capacity` to change that. -The classic events still work, including alongside a stream over the same event: +The classic handler sits on the same group, so subscribing and streaming read alike and there is no +second place to look: ```csharp -client.CurrentProgramSceneChanged += (_, e) => +client.Scenes.CurrentProgramSceneChanged += (_, e) => Console.WriteLine($"Program scene is now {e.EventData.SceneName}"); ``` +Both work over the same event at once. The group's event is the client's event, so a handler added +through one can be removed through the other; `client.CurrentProgramSceneChanged` remains for the +low-level path, the way `CallAsync` remains alongside the generated requests. + +Connection lifecycle events stay on the client, since `Connected`, `Disconnected`, +`ConnectionFailed` and `AuthenticationFailure` belong to no protocol category. + To wait for a single occurrence, use `WaitForEventAsync`. It subscribes before returning, so you can start the wait and then trigger the action without racing it: @@ -123,7 +202,8 @@ var intro = await client.WaitForEventAsync( ); ``` -It throws `TimeoutException` when the wait elapses. +It throws `ObsWebSocketTimeoutException` when the wait elapses, the same type a request +timeout raises, so one `catch (ObsWebSocketException)` covers both. ## Common use cases @@ -244,48 +324,154 @@ batch.Add("GetStats"); batch.Add("SetInputSettings", myJsonElement); ``` -> `RequestBatchExecutionType.Parallel` is best avoided when you care about the results. OBS collects -> them in completion order but labels them from the submission order, so every result carries -> another request's `responseData` and `requestStatus`. That happens before the response leaves OBS, -> so it cannot be corrected here; references refuse to resolve on such a batch and say why. See -> [#16](https://github.com/Agash/ObsWebSocket/issues/16). +### Running requests in parallel -## Typed protocol enums +`RequestBatchExecutionType.Parallel` works, but OBS mislabels what comes back. It collects results +in completion order and labels them from the submission order, so on any one row the +`requestType` and `requestId` belong to a different request than the `requestStatus` and +`responseData` beside them. That happens before the response leaves OBS, so it cannot be corrected +here. See [#16](https://github.com/Agash/ObsWebSocket/issues/16). -Protocol enums that travel as strings have a real C# enum, so states can be matched rather than -compared against constants: +Only the labelling is wrong. `requestStatus` and `responseData` come from the same object, so each +row's status does belong to the payload beside it; it is the `requestType` and `requestId` on that +row that name a different request. `Get` and the indexer therefore throw rather than hand back data +under the wrong reference, and `TryGet` reports `false`. + +Nothing is lost, though, and `Raw` still reaches all of it. `GetData` reads the payload without +consulting the label, so every response is recoverable as a set: ```csharp -client.StreamStateChanged += (_, e) => +BatchResults results = await client.CallBatchAsync( + batch, executionType: RequestBatchExecutionType.Parallel, haltOnFailure: false, cancellationToken: ct); + +foreach (RequestResponsePayload row in results.Raw) { - string what = OutputStateExtensions.FromWireValue(e.EventData.OutputState) switch + if (!row.RequestStatus.Result) { - OutputState.Started => "live", - OutputState.Starting or OutputState.Reconnecting => "coming up", - OutputState.Stopped or OutputState.Stopping => "going down", - null => $"unrecognised ({e.EventData.OutputState})", - _ => "in between", - }; + Console.WriteLine($"one request failed with {row.RequestStatus.Code}"); + continue; // the code is right, the requestType naming it is not + } - Console.WriteLine($"Stream is {what}"); -}; + // Correct data, from one of the requests in the batch. Which one is not knowable. + GetSceneItemListResponseData? data = row.GetData(); +} ``` -Media transport works the same way, with shorthands for the common actions: +That works when every request in the batch returns the **same** type, so it does not matter which +row is which, and when the order is not what you needed. + +A parallel batch of **different** request types is harder, because nothing on a row tells you which +type its payload really is. `GetData` will not invent an answer, though: it checks the payload +against the fields `T` expects and throws `ObsWebSocketSerializationException` when the payload +carries none of them. So you can try each type you expect and let the mismatch tell you. + +That check rejects rather than identifies. Two records that share field names cannot be told apart +this way, and a payload overlapping the target only partly still passes with the rest of the +properties left at their defaults. Where two records are field for field identical, which happens +for five shapes including `GetInputMute` and `ToggleInputMute`, reading one as the other gives the +right values anyway. + +So a heterogeneous parallel batch is possible to unpick but never reliable. Use a serial batch, or +concurrent requests. + +Anything that does not depend on which row is which stays exact: ```csharp -await client.MediaInputs.PlayMediaAsync("Stinger", ct); -await client.MediaInputs.TriggerMediaActionAsync("Stinger", MediaInputAction.Restart, ct); +ObsBatchBuilder batch = new(); +foreach (string input in inputs) +{ + _ = batch.Inputs.SetInputMute(new(inputName: input, inputMuted: true)); +} + +BatchResults results = await client.CallBatchAsync( + batch, executionType: RequestBatchExecutionType.Parallel, cancellationToken: ct); + +bool everythingWorked = results.AllSucceeded(); // reliable: order does not change the verdict +int failureCount = results.GetFailures().Count(); // reliable count, unreliable names +``` + +So `Parallel` suits a set of writes you want applied as fast as possible, where you only need to +know whether they all took. It does not suit reading anything back. + +When you need results attributed, use concurrent requests rather than a parallel batch. The client +multiplexes on the request id, so anything in flight at once is matched back to its own caller: + +```csharp +Task version = client.General.GetVersionAsync(ct); +Task stats = client.General.GetStatsAsync(ct); +Task[] perScene = +[ + .. sceneNames.Select(n => client.SceneItems.GetSceneItemListAsync(new(sceneName: n), ct)), +]; + +await Task.WhenAll([version, stats, .. perScene.Cast()]); + +Console.WriteLine(version.Result.ObsVersion); // each result belongs to its own request +``` + +That costs one round trip per request rather than one for the set. Use a serial batch when the round +trip is what you are saving, and concurrent requests when you need the answers attributed. + +## Dropping to the low level + +Nothing above is a wall. Every generated request is a thin wrapper over the same primitives, and +they stay available for a request this build does not model, an OBS newer than this library, or a +vendor plugin: + +```csharp +// A request with a reference type response. +GetVersionResponseData? v = await client.CallAsync("GetVersion", null, cancellationToken: ct); + +// A value type response, JsonElement included. CallAsync is constrained to classes, so a struct +// response goes through CallAsyncValue. +JsonElement? raw = await client.CallAsyncValue("GetStats", null, cancellationToken: ct); + +// Request data is written through a source generated context, so it must be a JsonElement, a type +// the library knows, or a type you supply metadata for. An anonymous object has no metadata +// anywhere and throws ObsWebSocketSerializationException. + +// Your own type, with your own context. AOT safe, and nothing to hand build. +[JsonSerializable(typeof(MyRequest))] +internal sealed partial class MyContext : JsonSerializerContext; + +JsonElement? answer = await client.CallAsyncValue( + "SomeNewRequest", new MyRequest(1), MyContext.Default.MyRequest, cancellationToken: ct); + +// Or a JsonElement built by hand, when a one-off payload does not deserve a type. +using JsonDocument body = JsonDocument.Parse("""{"someField":1}"""); +JsonElement? viaElement = await client.CallAsyncValue( + "SomeNewRequest", body.RootElement, cancellationToken: ct); + +// A batch assembled by hand, without the typed builder. +List> results = await client.CallBatchAsync( + [new BatchRequestItem("GetVersion", null), new BatchRequestItem("GetStats", null)], + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: ct); + +foreach (RequestResponsePayload result in results) +{ + GetVersionResponseData? data = result.GetData(); +} ``` -The wire constants remain available as `const` strings on `ObsOutputState` and `ObsMediaInputAction`, -and `ToWireValue()` converts an enum back. +Those two are the AOT-safe ways to build a payload. `JsonSerializer.SerializeToElement` without a +`JsonTypeInfo`, and the `JsonNode` and `JsonObject` routes, all work at runtime but carry `IL2026` +and `IL3050`, so they are not options under Native AOT. + +The same applies to events and enums: `client.SceneCreated` remains alongside +`client.Scenes.SceneCreated`, and `ToWireValue()` / `FromWireValue()` convert an enum to and from +the protocol string when you are building a payload by hand. -## Numbers +## Protocol types -The protocol has a single `Number` type because JSON does, so a scene item id and a volume -multiplier look identical in the definition. Fields that hold whole numbers are generated as `int` -or `long` rather than `double`: +The protocol definition is looser than C#: it has one numeric type because JSON does, and it types +enum-valued fields as plain strings. The generated surface narrows both, so callers get the C# type +rather than the wire representation. + +**Numbers.** A scene item id and a volume multiplier are both `Number` with a `>= 0` restriction, so +which ones are integral is not recoverable from the definition. Fields holding whole numbers are +generated as `int` or `long`, from an explicit list in the generator rather than a rule over field +names, so a volume can never be truncated by a naming coincidence: ```csharp int id = await client.SceneItems.FindSceneItemIdAsync("Intro", "Logo", ct) ?? throw new(...); @@ -295,8 +481,36 @@ long bytes = (await client.Stream.GetStreamStatusAsync(ct)).OutputBytes; double volume = (await client.Inputs.GetInputVolumeAsync(new("Mic"), ct)).InputVolumeMul; ``` -Which fields those are is an explicit list in the generator, not a rule over field names, so a -volume can never be silently truncated by a naming coincidence. +**Enums.** Fields carrying a protocol enum are that enum, on both the read and the write side, so +there is nothing to convert at the call site: + +```csharp +client.Outputs.StreamStateChanged += (_, e) => +{ + string what = e.EventData.OutputState switch + { + OutputState.Started => "live", + OutputState.Starting or OutputState.Reconnecting => "coming up", + OutputState.Stopped or OutputState.Stopping => "going down", + OutputState.Unknown => "in a state this build does not recognise", + _ => "in between", + }; +}; + +await client.MediaInputs.TriggerMediaActionAsync("Stinger", MediaInputAction.Restart, ct); +``` + +A value OBS sends that this build does not know maps to the enum's zero member rather than throwing, +so a state added by a newer OBS does not fail the whole message. + +This covers the enums the protocol declares. `mediaState`, `monitorType`, `sceneItemBlendMode` and +`inputKind` carry fixed vocabularies too, but the protocol types them as strings and never lists +their values, so they stay strings rather than being given an enum this library would have to keep +correct by hand. + +The wire values also remain as `const` strings on `ObsOutputState` and `ObsMediaInputAction`, and +`ToWireValue()` converts an enum back, for payloads built by hand. See +[Dropping to the low level](#dropping-to-the-low-level). ## Host integration @@ -359,7 +573,7 @@ try } catch (ObsWebSocketRequestException ex) { - Console.WriteLine($"{ex.RequestType} failed with {ex.Status?.Code}: {ex.Comment}"); + Console.WriteLine($"{ex.RequestType} failed with {ex.StatusCode}: {ex.Comment}"); } catch (ObsWebSocketTimeoutException) { @@ -391,6 +605,15 @@ Reconnect delays grow by `ReconnectBackoffMultiplier`, are capped at `MaxReconne jitter so several clients recovering from one outage do not retry in lockstep. Authentication failures are never retried, since they cannot succeed on a second attempt. +`WithReconnectPipeline()` registers the default pipeline explicitly, which is worth doing when a +host has its own resilience configuration and you want this client's to be visible alongside it: + +```csharp +builder.AddObsWebSocketClient("obs") + .WithAutoConnect() + .WithReconnectPipeline(); +``` + To replace the policy outright rather than tune those options, register your own pipeline under `ObsWebSocketResilience.ReconnectPipelineKey` after adding the client. @@ -426,10 +649,13 @@ identically on either, and the validation suite exercises both. - **One-shot mode**: `ObsWebSocket.Example run-transport-tests` `run-transport-tests` creates its own scene and input, so it does not depend on a particular OBS -layout, and removes them afterwards. On each transport it asserts real values for the settings modes, -event streams and buffering, `WaitForEventAsync`, the typed batch builder including duplicate request -types, partial failure and truncation, typed protocol enums, screenshots, and the scene, input, -volume and output helpers. +layout, and removes them afterwards. It runs the same checks on JSON and on MessagePack, asserting +real values rather than that a call returned: the three settings modes, event streams and their +buffering, `WaitForEventAsync`, the typed batch builder including duplicate request types, partial +failure and truncation, a parallel batch and what survives its mispairing, concurrent requests +keeping their own results, the low level `Add` and `CallAsync` path, typed protocol enums, integer +fields round tripping in both directions, screenshots in memory and on disk, and the scene, preview, +input, mute, volume, media, transition and output helpers. ## Native AOT