Core: Improve recording stability

This commit is contained in:
Genteure 2021-04-23 19:15:20 +08:00
parent 2e353da446
commit 539b0b95a4
4 changed files with 59 additions and 24 deletions

View File

@ -23,8 +23,6 @@ namespace BililiveRecorder.Core.Recording
public override void SplitOutput() { } public override void SplitOutput() { }
public override void RequestStop() => this.cts.Cancel();
protected override void StartRecordingLoop(Stream stream) protected override void StartRecordingLoop(Stream stream)
{ {
var (fullPath, relativePath) = this.CreateFileName(); var (fullPath, relativePath) = this.CreateFileName();
@ -79,10 +77,8 @@ namespace BililiveRecorder.Core.Recording
} }
finally finally
{ {
this.logger.Debug("录制退出");
this.timer.Stop(); this.timer.Stop();
this.cts.Cancel(); this.RequestStop();
try try
{ {
@ -104,10 +100,12 @@ namespace BililiveRecorder.Core.Recording
this.logger.Warning(ex, "Error calling OnRecordFileClosed"); this.logger.Warning(ex, "Error calling OnRecordFileClosed");
} }
stream.Dispose();
file.Dispose(); file.Dispose();
stream.Dispose();
this.OnRecordSessionEnded(EventArgs.Empty); this.OnRecordSessionEnded(EventArgs.Empty);
this.logger.Debug("录制退出");
} }
} }
} }

View File

@ -30,6 +30,7 @@ namespace BililiveRecorder.Core.Recording
protected readonly IApiClient apiClient; protected readonly IApiClient apiClient;
protected bool started = false; protected bool started = false;
protected bool timeoutTriggered = false;
private readonly object fillerStatsLock = new object(); private readonly object fillerStatsLock = new object();
protected int fillerDownloadedBytes; protected int fillerDownloadedBytes;
@ -85,6 +86,18 @@ namespace BililiveRecorder.Core.Recording
this.fillerStatsLastTrigger = DateTimeOffset.UtcNow; this.fillerStatsLastTrigger = DateTimeOffset.UtcNow;
this.durationSinceNoDataReceived = TimeSpan.Zero; this.durationSinceNoDataReceived = TimeSpan.Zero;
this.ct.Register(state => Task.Run(async () =>
{
try
{
await Task.Delay(1000);
if (((WeakReference<Stream>)state).TryGetTarget(out var weakStream))
weakStream.Dispose();
}
catch (Exception)
{ }
}), state: new WeakReference<Stream>(stream), useSynchronizationContext: false);
this.StartRecordingLoop(stream); this.StartRecordingLoop(stream);
} }
@ -118,8 +131,9 @@ namespace BililiveRecorder.Core.Recording
Mbps = mbps Mbps = mbps
}); });
if (this.durationSinceNoDataReceived.TotalMilliseconds > this.room.RoomConfig.TimingWatchdogTimeout) if ((!this.timeoutTriggered) && (this.durationSinceNoDataReceived.TotalMilliseconds > this.room.RoomConfig.TimingWatchdogTimeout))
{ {
this.timeoutTriggered = true;
this.logger.Warning("直播服务器未断开连接但停止发送直播数据,将会主动断开连接"); this.logger.Warning("直播服务器未断开连接但停止发送直播数据,将会主动断开连接");
this.RequestStop(); this.RequestStop();
} }

View File

@ -111,6 +111,7 @@ namespace BililiveRecorder.Core.Recording
{ {
const int minimumBufferSize = 1024; const int minimumBufferSize = 1024;
this.timer.Start(); this.timer.Start();
Exception? exception = null; Exception? exception = null;
try try
{ {
@ -188,15 +189,15 @@ namespace BililiveRecorder.Core.Recording
} }
finally finally
{ {
this.logger.Debug("录制退出");
this.reader?.Dispose(); this.reader?.Dispose();
this.reader = null; this.reader = null;
this.writer?.Dispose(); this.writer?.Dispose();
this.writer = null; this.writer = null;
this.cts.Cancel(); this.RequestStop();
this.OnRecordSessionEnded(EventArgs.Empty); this.OnRecordSessionEnded(EventArgs.Empty);
this.logger.Debug("录制退出");
} }
} }

View File

@ -127,7 +127,7 @@ namespace BililiveRecorder.Core
} }
catch (Exception ex) catch (Exception ex)
{ {
this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Debug : LogEventLevel.Warning, ex, "尝试开始录制时出错"); this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Verbose : LogEventLevel.Warning, ex, "尝试开始录制时出错");
} }
}); });
} }
@ -159,7 +159,7 @@ namespace BililiveRecorder.Core
} }
catch (Exception ex) catch (Exception ex)
{ {
this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Debug : LogEventLevel.Warning, ex, "刷新房间信息时出错"); this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Verbose : LogEventLevel.Warning, ex, "刷新房间信息时出错");
} }
} }
@ -220,13 +220,13 @@ namespace BililiveRecorder.Core
} }
catch (Exception ex) catch (Exception ex)
{ {
this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Debug : LogEventLevel.Warning, ex, "启动录制出错"); this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Verbose : LogEventLevel.Warning, ex, "启动录制出错");
this.recordTask = null; this.recordTask = null;
this.OnPropertyChanged(nameof(this.Recording)); this.OnPropertyChanged(nameof(this.Recording));
// 请求直播流出错时的重试逻辑 // 请求直播流出错时的重试逻辑
_ = Task.Run(async () => await this.RestartAfterRecordTaskFailedAsync().ConfigureAwait(false)); _ = Task.Run(this.RestartAfterRecordTaskFailedAsync);
return; return;
} }
@ -253,6 +253,11 @@ namespace BililiveRecorder.Core
{ {
await Task.Delay((int)this.RoomConfig.TimingStreamRetry, this.ct).ConfigureAwait(false); await Task.Delay((int)this.RoomConfig.TimingStreamRetry, this.ct).ConfigureAwait(false);
} }
catch (TaskCanceledException)
{
// 房间已经被删除
return;
}
finally finally
{ {
this.recordRetryDelaySemaphoreSlim.Release(); this.recordRetryDelaySemaphoreSlim.Release();
@ -266,12 +271,10 @@ namespace BililiveRecorder.Core
if (this.Streaming && this.AutoRecordForThisSession) if (this.Streaming && this.AutoRecordForThisSession)
this.CreateAndStartNewRecordTask(); this.CreateAndStartNewRecordTask();
} }
catch (TaskCanceledException) { }
catch (ExecutionRejectedException) { }
catch (Exception ex) catch (Exception ex)
{ {
this.logger.Write(LogEventLevel.Warning, ex, "重试开始录制时出错"); this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Verbose : LogEventLevel.Warning, ex, "重试开始录制时出错");
_ = Task.Run(async () => await this.RestartAfterRecordTaskFailedAsync().ConfigureAwait(false)); _ = Task.Run(this.RestartAfterRecordTaskFailedAsync);
} }
} }
@ -282,14 +285,21 @@ namespace BililiveRecorder.Core
try try
{ {
if (delay) if (delay)
try
{
await Task.Delay((int)this.RoomConfig.TimingDanmakuRetry, this.ct).ConfigureAwait(false); await Task.Delay((int)this.RoomConfig.TimingDanmakuRetry, this.ct).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
// 房间已被删除
return;
}
await this.danmakuClient.ConnectAsync(this.RoomConfig.RoomId, this.ct).ConfigureAwait(false); await this.danmakuClient.ConnectAsync(this.RoomConfig.RoomId, this.ct).ConfigureAwait(false);
} }
catch (TaskCanceledException) { }
catch (Exception ex) catch (Exception ex)
{ {
this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Debug : LogEventLevel.Warning, ex, "连接弹幕服务器时出错"); this.logger.Write(ex is ExecutionRejectedException ? LogEventLevel.Verbose : LogEventLevel.Warning, ex, "连接弹幕服务器时出错");
if (!this.ct.IsCancellationRequested) if (!this.ct.IsCancellationRequested)
this.StartDamakuConnection(); this.StartDamakuConnection();
@ -357,13 +367,23 @@ namespace BililiveRecorder.Core
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
// 录制结束退出后的重试逻辑 // 录制结束退出后的重试逻辑
// 比 RestartAfterRecordTaskFailedAsync 少了等待时间
if (!this.Streaming || !this.AutoRecordForThisSession) if (!this.Streaming || !this.AutoRecordForThisSession)
return; return;
try
{
await this.FetchRoomInfoAsync().ConfigureAwait(false); await this.FetchRoomInfoAsync().ConfigureAwait(false);
if (this.Streaming && this.AutoRecordForThisSession) if (this.Streaming && this.AutoRecordForThisSession)
this.CreateAndStartNewRecordTask(); this.CreateAndStartNewRecordTask();
}
catch (Exception ex)
{
this.logger.Write(LogEventLevel.Warning, ex, "重试开始录制时出错");
_ = Task.Run(this.RestartAfterRecordTaskFailedAsync);
}
}); });
} }
@ -423,6 +443,8 @@ namespace BililiveRecorder.Core
{ {
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
// 定时主动检查不需要错误重试
await this.FetchRoomInfoAsync().ConfigureAwait(false); await this.FetchRoomInfoAsync().ConfigureAwait(false);
if (this.Streaming && this.AutoRecordForThisSession && this.RoomConfig.AutoRecord) if (this.Streaming && this.AutoRecordForThisSession && this.RoomConfig.AutoRecord)