From 5f4c9633bd351989de6954fdcbbfcc185a81091f Mon Sep 17 00:00:00 2001 From: Genteure Date: Wed, 21 Apr 2021 23:18:23 +0800 Subject: [PATCH] Core: Re-write record trigger logic --- BililiveRecorder.Core/IRoom.cs | 1 + BililiveRecorder.Core/PollyPolicy.cs | 4 +- BililiveRecorder.Core/Room.cs | 153 +++++++++++------- BililiveRecorder.WPF/Controls/RoomCard.xaml | 16 +- BililiveRecorder.WPF/NewMainWindow.xaml | 2 +- BililiveRecorder.WPF/Pages/RoomListPage.xaml | 4 +- .../Resources/IconResources.xaml | 3 + 7 files changed, 112 insertions(+), 71 deletions(-) diff --git a/BililiveRecorder.Core/IRoom.cs b/BililiveRecorder.Core/IRoom.cs index 6da6cbc..69d1b3f 100644 --- a/BililiveRecorder.Core/IRoom.cs +++ b/BililiveRecorder.Core/IRoom.cs @@ -21,6 +21,7 @@ namespace BililiveRecorder.Core bool Recording { get; } bool Streaming { get; } bool DanmakuConnected { get; } + bool AutoRecordForThisSession { get; } RecordingStats Stats { get; } event EventHandler? RecordSessionStarted; diff --git a/BililiveRecorder.Core/PollyPolicy.cs b/BililiveRecorder.Core/PollyPolicy.cs index b35a5ac..3104a62 100644 --- a/BililiveRecorder.Core/PollyPolicy.cs +++ b/BililiveRecorder.Core/PollyPolicy.cs @@ -21,7 +21,7 @@ namespace BililiveRecorder.Core .Handle() .CircuitBreakerAsync( exceptionsAllowedBeforeBreaking: 1, - durationOfBreak: TimeSpan.FromMinutes(5), + durationOfBreak: TimeSpan.FromMinutes(2), onBreak: (_, _) => { logger.Warning("检测到被屏蔽,暂停发送请求"); @@ -59,7 +59,7 @@ namespace BililiveRecorder.Core var retry = Policy.Handle().RetryAsync(); - var bulkhead = Policy.BulkheadAsync(maxParallelization: 10, maxQueuingActions: 50); + var bulkhead = Policy.BulkheadAsync(maxParallelization: 5, maxQueuingActions: 200); this.memoryCache = new MemoryCache(new MemoryCacheOptions()); var memoryCacheProvider = new MemoryCacheProvider(this.memoryCache); diff --git a/BililiveRecorder.Core/Room.cs b/BililiveRecorder.Core/Room.cs index 0e3ba50..ac54ccb 100644 --- a/BililiveRecorder.Core/Room.cs +++ b/BililiveRecorder.Core/Room.cs @@ -41,8 +41,9 @@ namespace BililiveRecorder.Core private string title = string.Empty; private string areaNameParent = string.Empty; private string areaNameChild = string.Empty; - private bool streaming; private bool danmakuConnected; + private bool streaming; + private bool autoRecordForThisSession = true; private IRecordTask? recordTask; private DateTimeOffset recordTaskStartTime; @@ -62,8 +63,11 @@ namespace BililiveRecorder.Core this.cts = new CancellationTokenSource(); this.ct = this.cts.Token; + this.PropertyChanged += this.Room_PropertyChanged; this.RoomConfig.PropertyChanged += this.RoomConfig_PropertyChanged; + this.timer.Elapsed += this.Timer_Elapsed; + this.danmakuClient.StatusChanged += this.DanmakuClient_StatusChanged; this.danmakuClient.DanmakuReceived += this.DanmakuClient_DanmakuReceived; @@ -79,28 +83,14 @@ namespace BililiveRecorder.Core public string Title { get => this.title; private set => this.SetField(ref this.title, value); } public string AreaNameParent { get => this.areaNameParent; private set => this.SetField(ref this.areaNameParent, value); } public string AreaNameChild { get => this.areaNameChild; private set => this.SetField(ref this.areaNameChild, value); } - public bool Streaming - { - get => this.streaming; - private set - { - if (value == this.streaming) return; - // 从未开播状态切换为开播状态时重置允许录制状态 - var triggerRecord = value && !this.streaming; - if (triggerRecord) - this.AutoRecordAllowedForThisSession = true; + public bool Streaming { get => this.streaming; private set => this.SetField(ref this.streaming, value); } + + public bool AutoRecordForThisSession { get => this.autoRecordForThisSession; private set => this.SetField(ref this.autoRecordForThisSession, value); } - this.streaming = value; - this.OnPropertyChanged(nameof(this.Streaming)); - if (triggerRecord && this.RoomConfig.AutoRecord) - _ = Task.Run(() => this.CreateAndStartNewRecordTask()); - } - } public bool DanmakuConnected { get => this.danmakuConnected; private set => this.SetField(ref this.danmakuConnected, value); } - public bool Recording => this.recordTask != null; - public bool AutoRecordAllowedForThisSession { get; private set; } + public bool Recording => this.recordTask != null; public RoomConfig RoomConfig { get; } public RecordingStats Stats { get; } = new RecordingStats(); @@ -127,13 +117,13 @@ namespace BililiveRecorder.Core { lock (this.recordStartLock) { - this.AutoRecordAllowedForThisSession = true; + this.AutoRecordForThisSession = true; - _ = Task.Run(async () => + _ = Task.Run(() => { try { - await this.FetchRoomInfoThenCreateRecordTaskAsync().ConfigureAwait(false); + this.CreateAndStartNewRecordTask(); } catch (Exception ex) { @@ -147,7 +137,7 @@ namespace BililiveRecorder.Core { lock (this.recordStartLock) { - this.AutoRecordAllowedForThisSession = false; + this.AutoRecordForThisSession = false; if (this.recordTask == null) return; @@ -163,6 +153,9 @@ namespace BililiveRecorder.Core await this.FetchUserInfoAsync().ConfigureAwait(false); await this.FetchRoomInfoAsync().ConfigureAwait(false); this.StartDamakuConnection(delay: false); + + if (this.Streaming && this.AutoRecordForThisSession && this.RoomConfig.AutoRecord) + this.CreateAndStartNewRecordTask(); } catch (Exception ex) { @@ -194,13 +187,6 @@ namespace BililiveRecorder.Core this.Name = user.Data?.Info?.Name ?? this.Name; } - /// - private async Task FetchRoomInfoThenCreateRecordTaskAsync() - { - await this.FetchRoomInfoAsync().ConfigureAwait(false); - this.CreateAndStartNewRecordTask(); - } - /// private void CreateAndStartNewRecordTask() { @@ -237,8 +223,11 @@ namespace BililiveRecorder.Core this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Debug : LogEventLevel.Warning, ex, "启动录制出错"); this.recordTask = null; - _ = Task.Run(async () => await this.TryRestartRecordingAsync().ConfigureAwait(false)); this.OnPropertyChanged(nameof(this.Recording)); + + // 请求直播流出错时的重试逻辑 + _ = Task.Run(async () => await this.RestartAfterRecordTaskFailedAsync().ConfigureAwait(false)); + return; } RecordSessionStarted?.Invoke(this, new RecordSessionStartedEventArgs(this) @@ -250,37 +239,39 @@ namespace BililiveRecorder.Core } /// - private async Task TryRestartRecordingAsync(bool delay = true) + private async Task RestartAfterRecordTaskFailedAsync() { - if (this.AutoRecordAllowedForThisSession) + if (!this.Streaming || !this.AutoRecordForThisSession) + return; + + try { + if (!await this.recordRetryDelaySemaphoreSlim.WaitAsync(0).ConfigureAwait(false)) + return; + try { - if (delay) - { - if (!await this.recordRetryDelaySemaphoreSlim.WaitAsync(0).ConfigureAwait(false)) - return; - - try - { - await Task.Delay((int)this.RoomConfig.TimingStreamRetry, this.ct).ConfigureAwait(false); - } - finally - { - this.recordRetryDelaySemaphoreSlim.Release(); - } - } - - if (!this.AutoRecordAllowedForThisSession) - return; - - await this.FetchRoomInfoThenCreateRecordTaskAsync().ConfigureAwait(false); + await Task.Delay((int)this.RoomConfig.TimingStreamRetry, this.ct).ConfigureAwait(false); } - catch (TaskCanceledException) { } - catch (Exception ex) + finally { - this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Debug : LogEventLevel.Warning, ex, "重试开始录制时出错"); + this.recordRetryDelaySemaphoreSlim.Release(); } + + if (!this.Streaming || !this.AutoRecordForThisSession) + return; + + await this.FetchRoomInfoAsync().ConfigureAwait(false); + + if (this.Streaming && this.AutoRecordForThisSession) + this.CreateAndStartNewRecordTask(); + } + catch (TaskCanceledException) { } + catch (ExecutionRejectedException) { } + catch (Exception ex) + { + this.logger.Write(LogEventLevel.Warning, ex, "重试开始录制时出错"); + _ = Task.Run(async () => await this.RestartAfterRecordTaskFailedAsync().ConfigureAwait(false)); } } @@ -363,7 +354,17 @@ namespace BililiveRecorder.Core { id = this.recordTask?.SessionId ?? default; this.recordTask = null; - _ = Task.Run(async () => await this.TryRestartRecordingAsync(delay: false).ConfigureAwait(false)); + _ = Task.Run(async () => + { + // 录制结束退出后的重试逻辑 + if (!this.Streaming || !this.AutoRecordForThisSession) + return; + + await this.FetchRoomInfoAsync().ConfigureAwait(false); + + if (this.Streaming && this.AutoRecordForThisSession) + this.CreateAndStartNewRecordTask(); + }); } this.basicDanmakuWriter.Disable(); @@ -410,14 +411,44 @@ namespace BililiveRecorder.Core else { this.DanmakuConnected = false; - this.StartDamakuConnection(); + this.StartDamakuConnection(delay: true); } } private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { + this.StartDamakuConnection(delay: false); + if (this.RoomConfig.AutoRecord) - _ = Task.Run(async () => await this.TryRestartRecordingAsync(delay: false).ConfigureAwait(false)); + { + _ = Task.Run(async () => + { + await this.FetchRoomInfoAsync().ConfigureAwait(false); + + if (this.Streaming && this.AutoRecordForThisSession && this.RoomConfig.AutoRecord) + this.CreateAndStartNewRecordTask(); + }); + } + } + + private void Room_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(this.Streaming): + if (this.Streaming) + { + if (this.AutoRecordForThisSession && this.RoomConfig.AutoRecord) + this.CreateAndStartNewRecordTask(); + } + else + { + this.AutoRecordForThisSession = true; + } + break; + default: + break; + } } private void RoomConfig_PropertyChanged(object sender, PropertyChangedEventArgs e) @@ -432,7 +463,11 @@ namespace BililiveRecorder.Core break; case nameof(this.RoomConfig.AutoRecord): if (this.RoomConfig.AutoRecord) - this.AutoRecordAllowedForThisSession = true; + { + this.AutoRecordForThisSession = true; + if (this.Streaming && this.AutoRecordForThisSession) + this.CreateAndStartNewRecordTask(); + } break; default: break; diff --git a/BililiveRecorder.WPF/Controls/RoomCard.xaml b/BililiveRecorder.WPF/Controls/RoomCard.xaml index 2b41722..d65fe20 100644 --- a/BililiveRecorder.WPF/Controls/RoomCard.xaml +++ b/BililiveRecorder.WPF/Controls/RoomCard.xaml @@ -28,7 +28,6 @@ TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" FontFamily="Microsoft Yahei" Text="{Binding Name,Mode=OneWay}" ToolTip="{Binding Name,Mode=OneWay}" ContextMenu="{StaticResource CopyTextContextMenu}"/> - @@ -52,17 +51,20 @@ + Foreground="{Binding DanmakuConnected,Converter={StaticResource BooleanToDanmakuConnectedColorBrushConverter}}" + ToolTip="{Binding DanmakuConnected,Converter={StaticResource BooleanToDanmakuConnectedTooltipConverter}}"/> + Foreground="{Binding Streaming,Converter={StaticResource BooleanToLiveStatusColorBrushConverter}}" + ToolTip="{Binding Streaming,Converter={StaticResource BooleanToLiveStatusTooltipConverter}}"/> + + Visibility="{Binding ShortId,Converter={StaticResource ShortRoomIdToVisibilityConverter}}"/> + Visibility="{Binding ShortId,Converter={StaticResource ShortRoomIdToVisibilityConverter}}"/> diff --git a/BililiveRecorder.WPF/NewMainWindow.xaml b/BililiveRecorder.WPF/NewMainWindow.xaml index 64b5b40..b690abb 100644 --- a/BililiveRecorder.WPF/NewMainWindow.xaml +++ b/BililiveRecorder.WPF/NewMainWindow.xaml @@ -19,7 +19,7 @@ ui:TitleBar.IsBackEnabled="False" ui:TitleBar.IsBackButtonVisible="False" ui:TitleBar.IsIconVisible="True" - Width="960" Height="650" MinHeight="400" MinWidth="340" + Width="1000" Height="650" MinHeight="400" MinWidth="340" WindowStartupLocation="CenterScreen" FontFamily="Microsoft YaHei" Closing="Window_Closing" StateChanged="Window_StateChanged" diff --git a/BililiveRecorder.WPF/Pages/RoomListPage.xaml b/BililiveRecorder.WPF/Pages/RoomListPage.xaml index 821cbdc..2d41ff5 100644 --- a/BililiveRecorder.WPF/Pages/RoomListPage.xaml +++ b/BililiveRecorder.WPF/Pages/RoomListPage.xaml @@ -34,11 +34,11 @@ Null="{StaticResource AddRoomCardTemplate}"/> - - diff --git a/BililiveRecorder.WPF/Resources/IconResources.xaml b/BililiveRecorder.WPF/Resources/IconResources.xaml index 62903c8..64d1e6e 100644 --- a/BililiveRecorder.WPF/Resources/IconResources.xaml +++ b/BililiveRecorder.WPF/Resources/IconResources.xaml @@ -85,6 +85,9 @@ +