fix: FFmpeg握手失败后自动切换协议重试 + 修复表格横向滚动条
This commit is contained in:
@@ -155,7 +155,7 @@ public sealed partial class FfmpegService
|
||||
}
|
||||
|
||||
var observedAt = DateTimeOffset.UtcNow;
|
||||
var adapter = adapterFactory.GetByPlatform(session.LiveRoom.Platform);
|
||||
var adapter = adapterFactory.GetByPlatform(session.LiveRoom!.Platform);
|
||||
var liveStatus = await adapter.GetLiveStatusAsync(session.LiveRoom.RoomId);
|
||||
await liveRoomStatusService.ApplySnapshotAsync(session.LiveRoom, liveStatus, observedAt);
|
||||
await dbContext.SaveChangesAsync();
|
||||
@@ -577,14 +577,24 @@ public sealed partial class FfmpegService
|
||||
return true;
|
||||
}
|
||||
|
||||
if (runtime.StartupFailureKind != StartupFailureKind.StreamHandshake || runtime.HasRetriedWithRefreshedStream)
|
||||
if (runtime.StartupFailureKind != StartupFailureKind.StreamHandshake)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (runtime.HasRetriedWithRefreshedStream)
|
||||
{
|
||||
if (runtime.HasRetriedWithAlternateProtocol)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await TryRetryWithAlternateProtocolAsync(runtime, session, currentTask, observedAt, scope);
|
||||
}
|
||||
|
||||
var adapterFactory = scope.ServiceProvider.GetRequiredService<ILivePlatformAdapterFactory>();
|
||||
var liveRoomStatusService = scope.ServiceProvider.GetRequiredService<LiveRoomStatusService>();
|
||||
var adapter = adapterFactory.GetByPlatform(session.LiveRoom.Platform);
|
||||
var adapter = adapterFactory.GetByPlatform(session.LiveRoom!.Platform);
|
||||
var liveStatus = await adapter.GetLiveStatusAsync(session.LiveRoom.RoomId);
|
||||
await liveRoomStatusService.ApplySnapshotAsync(session.LiveRoom, liveStatus, observedAt);
|
||||
await dbContext.SaveChangesAsync();
|
||||
@@ -604,15 +614,52 @@ public sealed partial class FfmpegService
|
||||
currentTask.Id);
|
||||
|
||||
var refreshedStream = await adapter.GetStreamUrlAsync(session.LiveRoom.RoomId, session.PreferredQuality);
|
||||
session.MarkStarting(refreshedStream.SelectedUrl, session.OutputPathPattern ?? runtime.OutputPathPattern, observedAt);
|
||||
|
||||
var selectedUrl = refreshedStream.SelectedUrl;
|
||||
var selectedProtocol = refreshedStream.SelectedProtocol;
|
||||
var selectedQuality = refreshedStream.SelectedQuality;
|
||||
if (refreshedStream.AvailableQualities.Count > 1)
|
||||
{
|
||||
var alternateProtocol = refreshedStream.SelectedProtocol.Equals("flv", StringComparison.OrdinalIgnoreCase)
|
||||
? "hls"
|
||||
: "flv";
|
||||
var alternateOption = refreshedStream.AvailableQualities
|
||||
.Where(o => o.Protocol.Equals(alternateProtocol, StringComparison.OrdinalIgnoreCase) &&
|
||||
o.QualityKey == refreshedStream.SelectedQuality)
|
||||
.MaxBy(o => o.Rank);
|
||||
|
||||
if (alternateOption is not null)
|
||||
{
|
||||
selectedUrl = alternateOption.Url;
|
||||
selectedProtocol = alternateOption.Protocol;
|
||||
selectedQuality = alternateOption.QualityKey;
|
||||
await logService.WriteAsync(
|
||||
SystemLogLevel.Info,
|
||||
"FFmpeg",
|
||||
$"ffmpeg retry will use alternate protocol {selectedProtocol.ToUpperInvariant()} for the same quality tier.",
|
||||
liveRoomId: session.LiveRoomId,
|
||||
recordSessionId: session.Id,
|
||||
recordTaskId: currentTask.Id);
|
||||
}
|
||||
}
|
||||
|
||||
var streamForRetry = new StreamUrlResult(
|
||||
selectedQuality,
|
||||
selectedProtocol,
|
||||
selectedUrl,
|
||||
refreshedStream.InputHeaders,
|
||||
refreshedStream.AvailableQualities,
|
||||
refreshedStream.SelectedVideoCodec);
|
||||
|
||||
session.MarkStarting(streamForRetry.SelectedUrl, session.OutputPathPattern ?? runtime.OutputPathPattern, observedAt);
|
||||
session.ActivateSegment(Math.Max(1, runtime.CurrentSegmentIndex), observedAt);
|
||||
currentTask.MarkStarting(refreshedStream.SelectedUrl, currentTask.OutputFilePath ?? runtime.CurrentOutputFilePath, observedAt);
|
||||
currentTask.MarkStarting(streamForRetry.SelectedUrl, currentTask.OutputFilePath ?? runtime.CurrentOutputFilePath, observedAt);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
await StartInternalAsync(
|
||||
session,
|
||||
currentTask,
|
||||
refreshedStream,
|
||||
streamForRetry,
|
||||
runtime.RecordingSettings,
|
||||
runtime.InputOptionProfile,
|
||||
hasRetriedWithCompatibilityProfile: runtime.HasRetriedWithCompatibilityProfile,
|
||||
@@ -641,6 +688,107 @@ public sealed partial class FfmpegService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> TryRetryWithAlternateProtocolAsync(
|
||||
SessionProcessRuntime runtime,
|
||||
RecordSession session,
|
||||
RecordTask currentTask,
|
||||
DateTimeOffset observedAt,
|
||||
IServiceScope scope)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<LiveRecorderDbContext>();
|
||||
var logService = scope.ServiceProvider.GetRequiredService<ISystemLogService>();
|
||||
var adapterFactory = scope.ServiceProvider.GetRequiredService<ILivePlatformAdapterFactory>();
|
||||
var liveRoomStatusService = scope.ServiceProvider.GetRequiredService<LiveRoomStatusService>();
|
||||
var adapter = adapterFactory.GetByPlatform(session.LiveRoom!.Platform);
|
||||
var liveStatus = await adapter.GetLiveStatusAsync(session.LiveRoom.RoomId);
|
||||
await liveRoomStatusService.ApplySnapshotAsync(session.LiveRoom, liveStatus, observedAt);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
if (!liveStatus.IsLive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var refreshedStream = await adapter.GetStreamUrlAsync(session.LiveRoom.RoomId, session.PreferredQuality);
|
||||
var alternateProtocol = runtime.SelectedProtocol.Equals("flv", StringComparison.OrdinalIgnoreCase)
|
||||
? "hls"
|
||||
: "flv";
|
||||
|
||||
var alternateOption = refreshedStream.AvailableQualities
|
||||
.Where(o => o.Protocol.Equals(alternateProtocol, StringComparison.OrdinalIgnoreCase))
|
||||
.OrderByDescending(o => o.Rank)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (alternateOption is null)
|
||||
{
|
||||
await logService.WriteAsync(
|
||||
SystemLogLevel.Warning,
|
||||
"FFmpeg",
|
||||
$"No {alternateProtocol.ToUpperInvariant()} stream option available for alternate protocol fallback.",
|
||||
liveRoomId: session.LiveRoomId,
|
||||
recordSessionId: session.Id,
|
||||
recordTaskId: currentTask.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
await logService.WriteAsync(
|
||||
SystemLogLevel.Warning,
|
||||
"FFmpeg",
|
||||
$"Refreshed stream URL also failed. Trying alternate protocol {alternateProtocol.ToUpperInvariant()} as last fallback.",
|
||||
runtime.LastStartupFailureLine,
|
||||
session.LiveRoomId,
|
||||
session.Id,
|
||||
currentTask.Id);
|
||||
|
||||
var retryStream = new StreamUrlResult(
|
||||
alternateOption.QualityKey,
|
||||
alternateOption.Protocol,
|
||||
alternateOption.Url,
|
||||
refreshedStream.InputHeaders,
|
||||
refreshedStream.AvailableQualities,
|
||||
refreshedStream.SelectedVideoCodec);
|
||||
|
||||
session.MarkStarting(retryStream.SelectedUrl, session.OutputPathPattern ?? runtime.OutputPathPattern, observedAt);
|
||||
session.ActivateSegment(Math.Max(1, runtime.CurrentSegmentIndex), observedAt);
|
||||
currentTask.MarkStarting(retryStream.SelectedUrl, currentTask.OutputFilePath ?? runtime.CurrentOutputFilePath, observedAt);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
runtime.HasRetriedWithAlternateProtocol = true;
|
||||
|
||||
await StartInternalAsync(
|
||||
session,
|
||||
currentTask,
|
||||
retryStream,
|
||||
runtime.RecordingSettings,
|
||||
runtime.InputOptionProfile,
|
||||
hasRetriedWithCompatibilityProfile: runtime.HasRetriedWithCompatibilityProfile,
|
||||
hasRetriedWithRefreshedStream: true,
|
||||
runtime.RetryAttemptCount + 1);
|
||||
|
||||
var retryStartedAt = DateTimeOffset.UtcNow;
|
||||
session.MarkRunning(retryStartedAt);
|
||||
currentTask.MarkRunning(retryStartedAt);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
await logService.WriteAsync(
|
||||
SystemLogLevel.Info,
|
||||
"FFmpeg",
|
||||
$"ffmpeg alternate protocol ({alternateProtocol.ToUpperInvariant()}) retry started.",
|
||||
liveRoomId: session.LiveRoomId,
|
||||
recordSessionId: session.Id,
|
||||
recordTaskId: currentTask.Id);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Alternate protocol fallback failed for session {RecordSessionId}", runtime.RecordSessionId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FinalizeExitedSessionAsync(
|
||||
SessionProcessRuntime runtime,
|
||||
Process process,
|
||||
@@ -1399,6 +1547,7 @@ public sealed partial class FfmpegService
|
||||
public FfmpegInputOptionProfile InputOptionProfile { get; }
|
||||
public bool HasRetriedWithCompatibilityProfile { get; }
|
||||
public bool HasRetriedWithRefreshedStream { get; }
|
||||
public bool HasRetriedWithAlternateProtocol { get; set; }
|
||||
public Process? Process { get; private set; }
|
||||
public int ProcessId => Process?.Id ?? 0;
|
||||
public bool CompletionRequested { get; private set; }
|
||||
|
||||
Reference in New Issue
Block a user