Core: Re-write record trigger logic

This commit is contained in:
Genteure 2021-04-21 23:18:23 +08:00
parent fc0b955b3d
commit 5f4c9633bd
7 changed files with 112 additions and 71 deletions

View File

@ -21,6 +21,7 @@ namespace BililiveRecorder.Core
bool Recording { get; }
bool Streaming { get; }
bool DanmakuConnected { get; }
bool AutoRecordForThisSession { get; }
RecordingStats Stats { get; }
event EventHandler<RecordSessionStartedEventArgs>? RecordSessionStarted;

View File

@ -21,7 +21,7 @@ namespace BililiveRecorder.Core
.Handle<Http412Exception>()
.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<Exception>().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);

View File

@ -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;
}
/// <exception cref="Exception"/>
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;

View File

@ -28,7 +28,6 @@
TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" FontFamily="Microsoft Yahei"
Text="{Binding Name,Mode=OneWay}" ToolTip="{Binding Name,Mode=OneWay}"
ContextMenu="{StaticResource CopyTextContextMenu}"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@ -52,17 +51,20 @@
</StackPanel>
<StackPanel Grid.Row="3" VerticalAlignment="Center" HorizontalAlignment="Stretch" Orientation="Horizontal">
<ui:PathIcon Height="10" Margin="0,0,2,0" Style="{Binding DanmakuConnected,Converter={StaticResource BooleanToDanmakuConnectedStyleConverter}}"
Foreground="{Binding DanmakuConnected,Converter={StaticResource BooleanToDanmakuConnectedColorBrushConverter}}"
ToolTip="{Binding DanmakuConnected,Converter={StaticResource BooleanToDanmakuConnectedTooltipConverter}}"/>
Foreground="{Binding DanmakuConnected,Converter={StaticResource BooleanToDanmakuConnectedColorBrushConverter}}"
ToolTip="{Binding DanmakuConnected,Converter={StaticResource BooleanToDanmakuConnectedTooltipConverter}}"/>
<ui:PathIcon Height="10" Margin="0,0,3,0" Style="{StaticResource PathIconDataAccessPoint}"
Foreground="{Binding Streaming,Converter={StaticResource BooleanToLiveStatusColorBrushConverter}}"
ToolTip="{Binding Streaming,Converter={StaticResource BooleanToLiveStatusTooltipConverter}}"/>
Foreground="{Binding Streaming,Converter={StaticResource BooleanToLiveStatusColorBrushConverter}}"
ToolTip="{Binding Streaming,Converter={StaticResource BooleanToLiveStatusTooltipConverter}}"/>
<ui:PathIcon Height="10" Margin="0,0,3,0" Style="{StaticResource PathIconDataVideoOffOutline}"
Visibility="{Binding AutoRecordForThisSession,Converter={StaticResource InvertBooleanToVisibilityCollapsedConverter}}"
Foreground="DarkOrange" ToolTip="本次直播不再自动录制,结束直播时恢复"/>
<ui:PathIcon Height="10" Style="{StaticResource PathIconDataUpperCaseIdentifier}" />
<TextBlock Text="{Binding RoomConfig.RoomId, StringFormat=\{0\},Mode=OneWay}" ContextMenu="{StaticResource CopyTextContextMenu}" Margin="4,0" FontSize="11"/>
<ui:PathIcon Height="10" Style="{StaticResource PathIconDataLowerCaseIdentifier}" Margin="3,0"
Visibility="{Binding ShortId,Converter={StaticResource ShortRoomIdToVisibilityConverter}}"/>
Visibility="{Binding ShortId,Converter={StaticResource ShortRoomIdToVisibilityConverter}}"/>
<TextBlock Text="{Binding ShortId, StringFormat=\{0\},Mode=OneWay}" ContextMenu="{StaticResource CopyTextContextMenu}" FontSize="11"
Visibility="{Binding ShortId,Converter={StaticResource ShortRoomIdToVisibilityConverter}}"/>
Visibility="{Binding ShortId,Converter={StaticResource ShortRoomIdToVisibilityConverter}}"/>
</StackPanel>
<Menu Grid.Column="1" Grid.RowSpan="3" VerticalAlignment="Center" HorizontalAlignment="Right">
<MenuItem ToolTip="{l:Loc RoomCard_Menu_Tooltip}">

View File

@ -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"

View File

@ -34,11 +34,11 @@
Null="{StaticResource AddRoomCardTemplate}"/>
<converters:BoolToValueConverter x:Key="BooleanToUniformGridLayoutConverter">
<converters:BoolToValueConverter.TrueValue>
<ui:UniformGridLayout MinItemWidth="220" MinItemHeight="125"
<ui:UniformGridLayout MinItemWidth="230" MinItemHeight="125"
ItemsStretch="None" MinRowSpacing="7" MinColumnSpacing="5"/>
</converters:BoolToValueConverter.TrueValue>
<converters:BoolToValueConverter.FalseValue>
<ui:UniformGridLayout MinItemWidth="220" MinItemHeight="100"
<ui:UniformGridLayout MinItemWidth="230" MinItemHeight="100"
ItemsStretch="None" MinRowSpacing="7" MinColumnSpacing="5"/>
</converters:BoolToValueConverter.FalseValue>
</converters:BoolToValueConverter>

View File

@ -85,6 +85,9 @@
<Style TargetType="ui:PathIcon" x:Key="PathIconDataMagnifyScan">
<Setter Property="Data" Value="M17 22V20H20V17H22V20.5C22 20.89 21.84 21.24 21.54 21.54C21.24 21.84 20.89 22 20.5 22H17M7 22H3.5C3.11 22 2.76 21.84 2.46 21.54C2.16 21.24 2 20.89 2 20.5V17H4V20H7V22M17 2H20.5C20.89 2 21.24 2.16 21.54 2.46C21.84 2.76 22 3.11 22 3.5V7H20V4H17V2M7 2V4H4V7H2V3.5C2 3.11 2.16 2.76 2.46 2.46C2.76 2.16 3.11 2 3.5 2H7M10.5 6C13 6 15 8 15 10.5C15 11.38 14.75 12.2 14.31 12.9L17.57 16.16L16.16 17.57L12.9 14.31C12.2 14.75 11.38 15 10.5 15C8 15 6 13 6 10.5C6 8 8 6 10.5 6M10.5 8C9.12 8 8 9.12 8 10.5C8 11.88 9.12 13 10.5 13C11.88 13 13 11.88 13 10.5C13 9.12 11.88 8 10.5 8Z"/>
</Style>
<Style TargetType="ui:PathIcon" x:Key="PathIconDataVideoOffOutline">
<Setter Property="Data" Value="M3.41,1.86L2,3.27L4.73,6H4A1,1 0 0,0 3,7V17A1,1 0 0,0 4,18H16C16.21,18 16.39,17.92 16.55,17.82L19.73,21L21.14,19.59L12.28,10.73L3.41,1.86M5,16V8H6.73L14.73,16H5M15,8V10.61L21,16.61V6.5L17,10.5V7A1,1 0 0,0 16,6H10.39L12.39,8H15Z"/>
</Style>
<Style TargetType="ui:PathIcon" x:Key="PathIconData">
<Setter Property="Data" Value=""/>
</Style>