mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-12-24 11:26:00 +08:00
Core: Re-write record trigger logic
This commit is contained in:
parent
fc0b955b3d
commit
5f4c9633bd
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}">
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user