From 06a4c59bb764679db91ec7db279072ddf04d3bfe Mon Sep 17 00:00:00 2001 From: genteure Date: Sat, 20 Nov 2021 14:34:35 +0800 Subject: [PATCH] Core: Strictly enforce qn settings --- .../Configure/ConfigInstructions.gen.cs | 2 + BililiveRecorder.Core/Config/V2/Config.gen.cs | 15 +++++ .../NoMatchingQnValueException.cs | 24 +++++++ .../Recording/RecordTaskBase.cs | 46 ++++++------- .../RestartRecordingReason.cs | 15 +++++ BililiveRecorder.Core/Room.cs | 27 ++++++-- .../Pages/AdvancedSettingsPage.xaml | 6 ++ configV2.schema.json | 64 ++++++++++++------- config_gen/data.ts | 10 +++ 9 files changed, 154 insertions(+), 55 deletions(-) create mode 100644 BililiveRecorder.Core/NoMatchingQnValueException.cs create mode 100644 BililiveRecorder.Core/RestartRecordingReason.cs diff --git a/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs b/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs index b178344..497d499 100644 --- a/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs +++ b/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs @@ -30,6 +30,7 @@ namespace BililiveRecorder.Cli.Configure LiveApiHost, TimingCheckInterval, TimingStreamRetry, + TimingStreamRetryNoQn, TimingStreamConnect, TimingDanmakuRetry, TimingWatchdogTimeout, @@ -75,6 +76,7 @@ namespace BililiveRecorder.Cli.Configure GlobalConfig.Add(GlobalConfigProperties.LiveApiHost, new ConfigInstruction(config => config.HasLiveApiHost = false, (config, value) => config.LiveApiHost = value) { Name = "LiveApiHost", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.TimingCheckInterval, new ConfigInstruction(config => config.HasTimingCheckInterval = false, (config, value) => config.TimingCheckInterval = value) { Name = "TimingCheckInterval", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.TimingStreamRetry, new ConfigInstruction(config => config.HasTimingStreamRetry = false, (config, value) => config.TimingStreamRetry = value) { Name = "TimingStreamRetry", CanBeOptional = true }); + GlobalConfig.Add(GlobalConfigProperties.TimingStreamRetryNoQn, new ConfigInstruction(config => config.HasTimingStreamRetryNoQn = false, (config, value) => config.TimingStreamRetryNoQn = value) { Name = "TimingStreamRetryNoQn", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.TimingStreamConnect, new ConfigInstruction(config => config.HasTimingStreamConnect = false, (config, value) => config.TimingStreamConnect = value) { Name = "TimingStreamConnect", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.TimingDanmakuRetry, new ConfigInstruction(config => config.HasTimingDanmakuRetry = false, (config, value) => config.TimingDanmakuRetry = value) { Name = "TimingDanmakuRetry", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.TimingWatchdogTimeout, new ConfigInstruction(config => config.HasTimingWatchdogTimeout = false, (config, value) => config.TimingWatchdogTimeout = value) { Name = "TimingWatchdogTimeout", CanBeOptional = true }); diff --git a/BililiveRecorder.Core/Config/V2/Config.gen.cs b/BililiveRecorder.Core/Config/V2/Config.gen.cs index 9c3132d..bbd44bc 100644 --- a/BililiveRecorder.Core/Config/V2/Config.gen.cs +++ b/BililiveRecorder.Core/Config/V2/Config.gen.cs @@ -141,6 +141,11 @@ namespace BililiveRecorder.Core.Config.V2 /// public uint TimingStreamRetry => this.GetPropertyValue(); + /// + /// 录制无指定画质重连时间间隔 秒 + /// + public uint TimingStreamRetryNoQn => this.GetPropertyValue(); + /// /// 连接直播服务器超时时间 毫秒 /// @@ -302,6 +307,14 @@ namespace BililiveRecorder.Core.Config.V2 [JsonProperty(nameof(TimingStreamRetry)), EditorBrowsable(EditorBrowsableState.Never)] public Optional OptionalTimingStreamRetry { get => this.GetPropertyValueOptional(nameof(this.TimingStreamRetry)); set => this.SetPropertyValueOptional(value, nameof(this.TimingStreamRetry)); } + /// + /// 录制无指定画质重连时间间隔 秒 + /// + public uint TimingStreamRetryNoQn { get => this.GetPropertyValue(); set => this.SetPropertyValue(value); } + public bool HasTimingStreamRetryNoQn { get => this.GetPropertyHasValue(nameof(this.TimingStreamRetryNoQn)); set => this.SetPropertyHasValue(value, nameof(this.TimingStreamRetryNoQn)); } + [JsonProperty(nameof(TimingStreamRetryNoQn)), EditorBrowsable(EditorBrowsableState.Never)] + public Optional OptionalTimingStreamRetryNoQn { get => this.GetPropertyValueOptional(nameof(this.TimingStreamRetryNoQn)); set => this.SetPropertyValueOptional(value, nameof(this.TimingStreamRetryNoQn)); } + /// /// 连接直播服务器超时时间 毫秒 /// @@ -375,6 +388,8 @@ namespace BililiveRecorder.Core.Config.V2 public uint TimingStreamRetry => 6 * 1000; + public uint TimingStreamRetryNoQn => 90; + public uint TimingStreamConnect => 5 * 1000; public uint TimingDanmakuRetry => 9 * 1000; diff --git a/BililiveRecorder.Core/NoMatchingQnValueException.cs b/BililiveRecorder.Core/NoMatchingQnValueException.cs new file mode 100644 index 0000000..22e8e52 --- /dev/null +++ b/BililiveRecorder.Core/NoMatchingQnValueException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace BililiveRecorder.Core +{ + public class NoMatchingQnValueException : Exception + { + public NoMatchingQnValueException() + { + } + + public NoMatchingQnValueException(string message) : base(message) + { + } + + public NoMatchingQnValueException(string message, Exception innerException) : base(message, innerException) + { + } + + protected NoMatchingQnValueException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/BililiveRecorder.Core/Recording/RecordTaskBase.cs b/BililiveRecorder.Core/Recording/RecordTaskBase.cs index 30806e0..c55c57d 100644 --- a/BililiveRecorder.Core/Recording/RecordTaskBase.cs +++ b/BililiveRecorder.Core/Recording/RecordTaskBase.cs @@ -238,54 +238,46 @@ namespace BililiveRecorder.Core.Recording { const int DefaultQn = 10000; var selected_qn = DefaultQn; - int[] qns; - Api.Model.RoomPlayInfo.UrlInfoItem[]? url_infos; - var codecItem = await this.apiClient.GetCodecItemInStreamUrlAsync(roomid: roomid, qn: DefaultQn).ConfigureAwait(false); if (codecItem is null) throw new Exception("no supported stream url, qn: " + DefaultQn); + var qns = this.room.RoomConfig.RecordingQuality?.Split(new[] { ',', ',', '、', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => int.TryParse(x, out var num) ? num : -1) + .Where(x => x > 0) + .ToArray() + ?? Array.Empty(); + + // Select first avaiable qn + foreach (var qn in qns) { - try + if (codecItem.AcceptQn.Contains(qn)) { - qns = this.room.RoomConfig.RecordingQuality.Split(new[] { ',', ',', '、', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => int.TryParse(x, out var num) ? num : -1) - .Where(x => x > 0) - .ToArray(); - - foreach (var qn in qns) - { - if (codecItem.AcceptQn.Contains(qn)) - { - selected_qn = qn; - break; - } - } - - this.logger.Debug("设置画质 {QnSettings}, 可用画质 {AcceptQn}, 最终选择 {SelectedQn}", qns, codecItem.AcceptQn, selected_qn); - } - catch (Exception ex) - { - this.logger.Warning(ex, "判断录制画质时出错,将默认使用 原画(10000)"); - qns = new[] { DefaultQn }; - url_infos = codecItem.UrlInfos; + selected_qn = qn; + goto match_qn_success; } } + this.logger.Information("没有符合设置要求的画质,稍后再试。设置画质 {QnSettings}, 可用画质 {AcceptQn}", qns, codecItem.AcceptQn); + throw new NoMatchingQnValueException(); + + match_qn_success: + this.logger.Debug("设置画质 {QnSettings}, 可用画质 {AcceptQn}, 最终选择 {SelectedQn}", qns, codecItem.AcceptQn, selected_qn); + if (selected_qn != DefaultQn) { // 最终选择的 qn 与默认不同,需要重新请求一次 codecItem = await this.apiClient.GetCodecItemInStreamUrlAsync(roomid: roomid, qn: selected_qn).ConfigureAwait(false); if (codecItem is null) - throw new Exception("no supported stream url, qn: " + DefaultQn); + throw new Exception("no supported stream url, qn: " + selected_qn); } if (codecItem.CurrentQn != selected_qn || !qns.Contains(codecItem.CurrentQn)) this.logger.Warning("当前录制的画质是 {CurrentQn}", codecItem.CurrentQn); - url_infos = codecItem.UrlInfos; + var url_infos = codecItem.UrlInfos; if (url_infos is null || url_infos.Length == 0) throw new Exception("no url_info"); diff --git a/BililiveRecorder.Core/RestartRecordingReason.cs b/BililiveRecorder.Core/RestartRecordingReason.cs new file mode 100644 index 0000000..ab4b70a --- /dev/null +++ b/BililiveRecorder.Core/RestartRecordingReason.cs @@ -0,0 +1,15 @@ +namespace BililiveRecorder.Core +{ + public enum RestartRecordingReason + { + /// + /// 普通重试 + /// + GenericRetry, + + /// + /// 无对应直播画质 + /// + NoMatchingQnValue, + } +} diff --git a/BililiveRecorder.Core/Room.cs b/BililiveRecorder.Core/Room.cs index 630ee6a..5208339 100644 --- a/BililiveRecorder.Core/Room.cs +++ b/BililiveRecorder.Core/Room.cs @@ -235,6 +235,16 @@ namespace BililiveRecorder.Core { await this.recordTask.StartAsync(); } + catch (NoMatchingQnValueException) + { + this.recordTask = null; + this.OnPropertyChanged(nameof(this.Recording)); + + // 无匹配的画质,重试录制之前等待更长时间 + _ = Task.Run(() => this.RestartAfterRecordTaskFailedAsync(RestartRecordingReason.NoMatchingQnValue)); + + return; + } catch (Exception ex) { this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Verbose : LogEventLevel.Warning, ex, "启动录制出错"); @@ -243,10 +253,11 @@ namespace BililiveRecorder.Core this.OnPropertyChanged(nameof(this.Recording)); // 请求直播流出错时的重试逻辑 - _ = Task.Run(this.RestartAfterRecordTaskFailedAsync); + _ = Task.Run(() => this.RestartAfterRecordTaskFailedAsync(RestartRecordingReason.GenericRetry)); return; } + RecordSessionStarted?.Invoke(this, new RecordSessionStartedEventArgs(this) { SessionId = this.recordTask.SessionId @@ -256,7 +267,7 @@ namespace BililiveRecorder.Core } /// - private async Task RestartAfterRecordTaskFailedAsync() + private async Task RestartAfterRecordTaskFailedAsync(RestartRecordingReason restartRecordingReason) { if (this.disposedValue) return; @@ -270,7 +281,13 @@ namespace BililiveRecorder.Core try { - await Task.Delay((int)this.RoomConfig.TimingStreamRetry, this.ct).ConfigureAwait(false); + var delay = restartRecordingReason switch + { + RestartRecordingReason.GenericRetry => this.RoomConfig.TimingStreamRetry, + RestartRecordingReason.NoMatchingQnValue => this.RoomConfig.TimingStreamRetryNoQn * 1000, + _ => throw new InvalidOperationException() + }; + await Task.Delay((int)delay, this.ct).ConfigureAwait(false); } catch (TaskCanceledException) { @@ -293,7 +310,7 @@ namespace BililiveRecorder.Core catch (Exception ex) { this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Verbose : LogEventLevel.Warning, ex, "重试开始录制时出错"); - _ = Task.Run(this.RestartAfterRecordTaskFailedAsync); + _ = Task.Run(() => this.RestartAfterRecordTaskFailedAsync(restartRecordingReason)); } } @@ -403,7 +420,7 @@ namespace BililiveRecorder.Core catch (Exception ex) { this.logger.Write(LogEventLevel.Warning, ex, "重试开始录制时出错"); - _ = Task.Run(this.RestartAfterRecordTaskFailedAsync); + _ = Task.Run(() => this.RestartAfterRecordTaskFailedAsync(RestartRecordingReason.GenericRetry)); } }); } diff --git a/BililiveRecorder.WPF/Pages/AdvancedSettingsPage.xaml b/BililiveRecorder.WPF/Pages/AdvancedSettingsPage.xaml index b8fc817..6e6fdde 100644 --- a/BililiveRecorder.WPF/Pages/AdvancedSettingsPage.xaml +++ b/BililiveRecorder.WPF/Pages/AdvancedSettingsPage.xaml @@ -56,6 +56,12 @@ + + + 无匹配的画质后等待多长时间再尝试开始录制 + + + diff --git a/configV2.schema.json b/configV2.schema.json index 2c92967..3d61c5c 100644 --- a/configV2.schema.json +++ b/configV2.schema.json @@ -8,7 +8,7 @@ "properties": { "RecordFilenameFormat": { "description": "录制文件名格式\n默认: {roomid}-{name}/录制-{roomid}-{date}-{time}-{ms}-{title}.flv", - "markdownDescription": "录制文件名格式 \n默认: `{roomid}-{name}/录制-{roomid}-{date}-{time}-{ms}-{title}.flv `\n\n", + "markdownDescription": "录制文件名格式 \n默认: `{roomid}-{name}/录制-{roomid}-{date}-{time}-{ms}-{title}.flv `\n\n- 只支持 FLV 格式\n- 所有大括号均为英文半角括号\n- 录制时如果出现文件名冲突,会使用一个默认文件名\n\n变量 | 含义\n:--:|:--:\n{date} | 当前日期(年月日)\n{time} | 当前时间(时分秒)\n{ms} | 当前时间毫秒\n{roomid} | 房间号\n{title} | 标题\n{name} | 主播名\n{parea} | 大分区\n{area} | 子分区\n{random} | 随机数字\n", "type": "object", "additionalProperties": false, "properties": { @@ -24,7 +24,7 @@ }, "WebHookUrls": { "description": "WebhookV1\n默认: ", - "markdownDescription": "WebhookV1 \n默认: ` `\n\n", + "markdownDescription": "WebhookV1 \n默认: ` `\n\n具体文档见 [Webhook](/docs/basic/webhook/)", "type": "object", "additionalProperties": false, "properties": { @@ -40,7 +40,7 @@ }, "WebHookUrlsV2": { "description": "WebhookV2\n默认: ", - "markdownDescription": "WebhookV2 \n默认: ` `\n\n", + "markdownDescription": "WebhookV2 \n默认: ` `\n\n具体文档见 [Webhook](/docs/basic/webhook/)", "type": "object", "additionalProperties": false, "properties": { @@ -56,7 +56,7 @@ }, "WpfShowTitleAndArea": { "description": "在界面显示标题和分区\n默认: true", - "markdownDescription": "在界面显示标题和分区 \n默认: `true `\n\n", + "markdownDescription": "在界面显示标题和分区 \n默认: `true `\n\n只在桌面版(WPF版)有效", "type": "object", "additionalProperties": false, "properties": { @@ -139,6 +139,24 @@ } } }, + "TimingStreamRetryNoQn": { + "description": "录制无指定画质重连时间间隔 秒\n默认: 90 (1.5分钟)", + "markdownDescription": "录制无指定画质重连时间间隔 秒 \n默认: `90 (1.5分钟) `\n\n", + "type": "object", + "additionalProperties": false, + "properties": { + "HasValue": { + "type": "boolean", + "default": true + }, + "Value": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295, + "default": 90 + } + } + }, "TimingStreamConnect": { "description": "连接直播服务器超时时间 毫秒\n默认: 5000 (5秒)", "markdownDescription": "连接直播服务器超时时间 毫秒 \n默认: `5000 (5秒) `\n\n", @@ -213,7 +231,7 @@ }, "RecordMode": { "description": "录制模式\n默认: RecordMode.Standard", - "markdownDescription": "录制模式 \n默认: `RecordMode.Standard `\n\n", + "markdownDescription": "录制模式 \n默认: `RecordMode.Standard `\n\n本设置项是一个 enum,键值对应如下:\n\n| 键 | 值 |\n|:--:|:--:|\n| RecordMode.Standard | 0 |\n| RecordMode.RawData | 1 |\n\n关于录制模式的说明见 [录制模式](/docs/basic/record_mode/)", "type": "object", "additionalProperties": false, "properties": { @@ -234,7 +252,7 @@ }, "CuttingMode": { "description": "自动分段模式\n默认: CuttingMode.Disabled", - "markdownDescription": "自动分段模式 \n默认: `CuttingMode.Disabled `\n\n", + "markdownDescription": "自动分段模式 \n默认: `CuttingMode.Disabled `\n\n本设置项是一个 enum,键值对应如下:\n\n| 键 | 值 |\n|:--:|:--:|\n| CuttingMode.Disabled | 0 |\n| CuttingMode.ByTime | 1 |\n| CuttingMode.BySize | 2 |", "type": "object", "additionalProperties": false, "properties": { @@ -256,7 +274,7 @@ }, "CuttingNumber": { "description": "自动分段数值\n默认: 100", - "markdownDescription": "自动分段数值 \n默认: `100 `\n\n按时长分段时为分钟,按大小分段时为MiB", + "markdownDescription": "自动分段数值 \n默认: `100 `\n\n根据 CuttingMode 设置的不同: \n当按时长分段时,本设置的单位为分钟。 \n当按大小分段时,本设置的单位为MiB。", "type": "object", "additionalProperties": false, "properties": { @@ -274,7 +292,7 @@ }, "RecordDanmaku": { "description": "弹幕录制\n默认: false", - "markdownDescription": "弹幕录制 \n默认: `false `\n\n", + "markdownDescription": "弹幕录制 \n默认: `false `\n\n是否录制弹幕,`true` 为录制,`false` 为不录制。\n\n本设置同时是所有“弹幕录制”的总开关,当本设置为 `false` 时其他所有“弹幕录制”设置无效,不会写入弹幕XML文件。", "type": "object", "additionalProperties": false, "properties": { @@ -290,7 +308,7 @@ }, "RecordDanmakuRaw": { "description": "弹幕录制-原始数据\n默认: false", - "markdownDescription": "弹幕录制-原始数据 \n默认: `false `\n\n", + "markdownDescription": "弹幕录制-原始数据 \n默认: `false `\n\n是否记录原始 JSON 数据。\n\n弹幕原始数据会保存到 XML 文件每一条弹幕数据的 `raw` attribute 上。\n\n当 `RecordDanmaku` 为 `false` 时本项设置无效。", "type": "object", "additionalProperties": false, "properties": { @@ -306,7 +324,7 @@ }, "RecordDanmakuSuperChat": { "description": "弹幕录制-SuperChat\n默认: true", - "markdownDescription": "弹幕录制-SuperChat \n默认: `true `\n\n", + "markdownDescription": "弹幕录制-SuperChat \n默认: `true `\n\n是否记录 SuperChat。\n\n当 `RecordDanmaku` 为 `false` 时本项设置无效。", "type": "object", "additionalProperties": false, "properties": { @@ -322,7 +340,7 @@ }, "RecordDanmakuGift": { "description": "弹幕录制-礼物\n默认: false", - "markdownDescription": "弹幕录制-礼物 \n默认: `false `\n\n", + "markdownDescription": "弹幕录制-礼物 \n默认: `false `\n\n是否记录礼物。\n\n当 `RecordDanmaku` 为 `false` 时本项设置无效。", "type": "object", "additionalProperties": false, "properties": { @@ -338,7 +356,7 @@ }, "RecordDanmakuGuard": { "description": "弹幕录制-上船\n默认: true", - "markdownDescription": "弹幕录制-上船 \n默认: `true `\n\n", + "markdownDescription": "弹幕录制-上船 \n默认: `true `\n\n是否记录上船(购买舰长)。\n\n当 `RecordDanmaku` 为 `false` 时本项设置无效。", "type": "object", "additionalProperties": false, "properties": { @@ -354,7 +372,7 @@ }, "RecordingQuality": { "description": "直播画质\n默认: 10000", - "markdownDescription": "直播画质 \n默认: `10000 `\n\n录制的直播画质 qn 值,逗号分割,靠前的优先", + "markdownDescription": "直播画质 \n默认: `10000 `\n\n录制的直播画质 qn 值,以英文逗号分割,靠前的优先。\n\n**注意**(从录播姬 1.3.10 开始):\n\n- 所有主播刚开播时都是只有“原画”的,如果选择不录原画会导致直播开头漏录。\n- 如果设置的录制画质里没有原画,但是主播只有原画画质,会导致不能录制直播。\n- 录播姬不会为了切换录制的画质主动断开录制。\n\n画质 | qn 值\n:--:|:--:\n4K | 20000\n原画 | 10000\n蓝光(杜比) | 401\n蓝光 | 400\n超清 | 250\n高清 | 150\n流畅 | 80", "type": "object", "additionalProperties": false, "properties": { @@ -393,7 +411,7 @@ }, "AutoRecord": { "description": "自动录制\n默认: default", - "markdownDescription": "自动录制 \n默认: `default `\n\n", + "markdownDescription": "自动录制 \n默认: `default `\n\n设为 `true` 为启用自动录制,`false` 为不自动录制。", "type": "object", "additionalProperties": false, "properties": { @@ -408,7 +426,7 @@ }, "RecordMode": { "description": "录制模式\n默认: RecordMode.Standard", - "markdownDescription": "录制模式 \n默认: `RecordMode.Standard `\n\n", + "markdownDescription": "录制模式 \n默认: `RecordMode.Standard `\n\n本设置项是一个 enum,键值对应如下:\n\n| 键 | 值 |\n|:--:|:--:|\n| RecordMode.Standard | 0 |\n| RecordMode.RawData | 1 |\n\n关于录制模式的说明见 [录制模式](/docs/basic/record_mode/)", "type": "object", "additionalProperties": false, "properties": { @@ -429,7 +447,7 @@ }, "CuttingMode": { "description": "自动分段模式\n默认: CuttingMode.Disabled", - "markdownDescription": "自动分段模式 \n默认: `CuttingMode.Disabled `\n\n", + "markdownDescription": "自动分段模式 \n默认: `CuttingMode.Disabled `\n\n本设置项是一个 enum,键值对应如下:\n\n| 键 | 值 |\n|:--:|:--:|\n| CuttingMode.Disabled | 0 |\n| CuttingMode.ByTime | 1 |\n| CuttingMode.BySize | 2 |", "type": "object", "additionalProperties": false, "properties": { @@ -451,7 +469,7 @@ }, "CuttingNumber": { "description": "自动分段数值\n默认: 100", - "markdownDescription": "自动分段数值 \n默认: `100 `\n\n按时长分段时为分钟,按大小分段时为MiB", + "markdownDescription": "自动分段数值 \n默认: `100 `\n\n根据 CuttingMode 设置的不同: \n当按时长分段时,本设置的单位为分钟。 \n当按大小分段时,本设置的单位为MiB。", "type": "object", "additionalProperties": false, "properties": { @@ -469,7 +487,7 @@ }, "RecordDanmaku": { "description": "弹幕录制\n默认: false", - "markdownDescription": "弹幕录制 \n默认: `false `\n\n", + "markdownDescription": "弹幕录制 \n默认: `false `\n\n是否录制弹幕,`true` 为录制,`false` 为不录制。\n\n本设置同时是所有“弹幕录制”的总开关,当本设置为 `false` 时其他所有“弹幕录制”设置无效,不会写入弹幕XML文件。", "type": "object", "additionalProperties": false, "properties": { @@ -485,7 +503,7 @@ }, "RecordDanmakuRaw": { "description": "弹幕录制-原始数据\n默认: false", - "markdownDescription": "弹幕录制-原始数据 \n默认: `false `\n\n", + "markdownDescription": "弹幕录制-原始数据 \n默认: `false `\n\n是否记录原始 JSON 数据。\n\n弹幕原始数据会保存到 XML 文件每一条弹幕数据的 `raw` attribute 上。\n\n当 `RecordDanmaku` 为 `false` 时本项设置无效。", "type": "object", "additionalProperties": false, "properties": { @@ -501,7 +519,7 @@ }, "RecordDanmakuSuperChat": { "description": "弹幕录制-SuperChat\n默认: true", - "markdownDescription": "弹幕录制-SuperChat \n默认: `true `\n\n", + "markdownDescription": "弹幕录制-SuperChat \n默认: `true `\n\n是否记录 SuperChat。\n\n当 `RecordDanmaku` 为 `false` 时本项设置无效。", "type": "object", "additionalProperties": false, "properties": { @@ -517,7 +535,7 @@ }, "RecordDanmakuGift": { "description": "弹幕录制-礼物\n默认: false", - "markdownDescription": "弹幕录制-礼物 \n默认: `false `\n\n", + "markdownDescription": "弹幕录制-礼物 \n默认: `false `\n\n是否记录礼物。\n\n当 `RecordDanmaku` 为 `false` 时本项设置无效。", "type": "object", "additionalProperties": false, "properties": { @@ -533,7 +551,7 @@ }, "RecordDanmakuGuard": { "description": "弹幕录制-上船\n默认: true", - "markdownDescription": "弹幕录制-上船 \n默认: `true `\n\n", + "markdownDescription": "弹幕录制-上船 \n默认: `true `\n\n是否记录上船(购买舰长)。\n\n当 `RecordDanmaku` 为 `false` 时本项设置无效。", "type": "object", "additionalProperties": false, "properties": { @@ -549,7 +567,7 @@ }, "RecordingQuality": { "description": "直播画质\n默认: 10000", - "markdownDescription": "直播画质 \n默认: `10000 `\n\n录制的直播画质 qn 值,逗号分割,靠前的优先", + "markdownDescription": "直播画质 \n默认: `10000 `\n\n录制的直播画质 qn 值,以英文逗号分割,靠前的优先。\n\n**注意**(从录播姬 1.3.10 开始):\n\n- 所有主播刚开播时都是只有“原画”的,如果选择不录原画会导致直播开头漏录。\n- 如果设置的录制画质里没有原画,但是主播只有原画画质,会导致不能录制直播。\n- 录播姬不会为了切换录制的画质主动断开录制。\n\n画质 | qn 值\n:--:|:--:\n4K | 20000\n原画 | 10000\n蓝光(杜比) | 401\n蓝光 | 400\n超清 | 250\n高清 | 150\n流畅 | 80", "type": "object", "additionalProperties": false, "properties": { diff --git a/config_gen/data.ts b/config_gen/data.ts index b5ab147..be4554c 100644 --- a/config_gen/data.ts +++ b/config_gen/data.ts @@ -165,6 +165,16 @@ export const data: Array = [ defaultValueDescription: "6000 (6秒)", markdown: "" }, + { + name: "TimingStreamRetryNoQn", + description: "录制无指定画质重连时间间隔 秒", + type: "uint", + configType: "globalOnly", + advancedConfig: true, + defaultValue: "90", + defaultValueDescription: "90 (1.5分钟)", + markdown: "" + }, { name: "TimingStreamConnect", description: "连接直播服务器超时时间 毫秒",