feat: harden recording lifecycle and refresh fnOS UI
This commit is contained in:
@@ -46,13 +46,19 @@ public sealed class RecoveryService
|
||||
Storage = new StorageGuardStatusDto
|
||||
{
|
||||
IsEnabled = storage.IsEnabled,
|
||||
IsAvailable = storage.IsAvailable,
|
||||
HasEnoughSpace = storage.HasEnoughSpace,
|
||||
CheckedPath = storage.CheckedPath,
|
||||
TotalBytes = storage.TotalBytes,
|
||||
UsedBytes = storage.UsedBytes,
|
||||
AvailableBytes = storage.AvailableBytes,
|
||||
RequiredBytes = storage.RequiredBytes,
|
||||
Message = storage.Message,
|
||||
Tier = storage.Tier.ToString(),
|
||||
UsagePercent = storage.UsagePercent
|
||||
UsagePercent = storage.UsagePercent,
|
||||
FreePercent = storage.FreePercent,
|
||||
GreenThresholdPercent = storage.GreenThresholdPercent,
|
||||
RedThresholdPercent = storage.RedThresholdPercent
|
||||
},
|
||||
LiveRooms = liveRooms,
|
||||
Finalizations = finalizations
|
||||
@@ -64,17 +70,17 @@ public sealed class RecoveryService
|
||||
var room = await _dbContext.LiveRooms.FirstOrDefaultAsync(item => item.Id == liveRoomId, cancellationToken);
|
||||
if (room is null)
|
||||
{
|
||||
return FailureResult("Live room was not found.");
|
||||
return FailureResult("未找到该直播间。");
|
||||
}
|
||||
|
||||
if (!room.IsEnabled)
|
||||
{
|
||||
return FailureResult("Live room is disabled and cannot be retried.");
|
||||
return FailureResult("该直播间已禁用,无法重试开录。");
|
||||
}
|
||||
|
||||
if (room.AvailabilityStatus != LiveRoomAvailabilityStatus.Live)
|
||||
{
|
||||
return FailureResult("Live room is not currently online.");
|
||||
return FailureResult("该直播间当前未开播,无法重试开录。");
|
||||
}
|
||||
|
||||
var hasActiveSession = await _dbContext.RecordSessions.AnyAsync(
|
||||
@@ -88,11 +94,11 @@ public sealed class RecoveryService
|
||||
{
|
||||
room.SetLastAutoStartDecision(
|
||||
AutoStartDecisionCodes.SkippedActiveSession,
|
||||
"Auto-start skipped because an active recording session already exists.",
|
||||
"已有活动录制会话,本次自动开录已跳过。",
|
||||
null,
|
||||
DateTimeOffset.UtcNow);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
return FailureResult("An active recording session already exists for this live room.");
|
||||
return FailureResult("该直播间已经存在活动录制会话。");
|
||||
}
|
||||
|
||||
try
|
||||
@@ -114,14 +120,14 @@ public sealed class RecoveryService
|
||||
Messages =
|
||||
[
|
||||
started
|
||||
? $"Recording retry started for room {room.RoomId}."
|
||||
: $"Recording retry did not start for room {room.RoomId}. Status={task.Status}; Error={task.ErrorMessage ?? "n/a"}"
|
||||
? $"直播间 {room.RoomId} 已开始重试录制。"
|
||||
: $"直播间 {room.RoomId} 未能开始录制。状态={task.Status};错误={task.ErrorMessage ?? "无"}"
|
||||
]
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return FailureResult($"Recording retry failed for room {room.RoomId}: {ex.Message}");
|
||||
return FailureResult($"直播间 {room.RoomId} 重试录制失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +141,7 @@ public sealed class RecoveryService
|
||||
RequestedCount = 0,
|
||||
SuccessCount = 0,
|
||||
FailedCount = 0,
|
||||
Messages = ["No live rooms currently require retry."]
|
||||
Messages = ["当前没有需要重试开录的直播间。"]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -159,7 +165,8 @@ public sealed class RecoveryService
|
||||
|
||||
public async Task<RecoveryActionResultDto> ResumeFinalizationAsync(Guid recordTaskId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var started = await _ffmpegService.StartManualFinalizeTaskAsync(recordTaskId, cancellationToken);
|
||||
var recoveredOrphan = await _ffmpegService.TryRecoverOrphanedTerminalTaskAsync(recordTaskId, cancellationToken);
|
||||
var started = recoveredOrphan || await _ffmpegService.StartManualFinalizeTaskAsync(recordTaskId, cancellationToken);
|
||||
return new RecoveryActionResultDto
|
||||
{
|
||||
RequestedCount = 1,
|
||||
@@ -168,8 +175,10 @@ public sealed class RecoveryService
|
||||
Messages =
|
||||
[
|
||||
started
|
||||
? $"MP4 finalization resumed for task {recordTaskId}."
|
||||
: $"MP4 finalization could not be resumed for task {recordTaskId}."
|
||||
? recoveredOrphan
|
||||
? $"任务 {recordTaskId} 的遗留分片已恢复,并已进入后续上传流程。"
|
||||
: $"任务 {recordTaskId} 已恢复 MP4 转码。"
|
||||
: $"任务 {recordTaskId} 无法恢复 MP4 转码。"
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -184,7 +193,7 @@ public sealed class RecoveryService
|
||||
RequestedCount = 0,
|
||||
SuccessCount = 0,
|
||||
FailedCount = 0,
|
||||
Messages = ["No MP4 finalization tasks currently require recovery."]
|
||||
Messages = ["当前没有需要恢复的 MP4 转码任务。"]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -192,7 +201,8 @@ public sealed class RecoveryService
|
||||
var messages = new List<string>();
|
||||
foreach (var item in finalizations)
|
||||
{
|
||||
var started = await _ffmpegService.StartManualFinalizeTaskAsync(item.RecordTaskId, cancellationToken);
|
||||
var recoveredOrphan = await _ffmpegService.TryRecoverOrphanedTerminalTaskAsync(item.RecordTaskId, cancellationToken);
|
||||
var started = recoveredOrphan || await _ffmpegService.StartManualFinalizeTaskAsync(item.RecordTaskId, cancellationToken);
|
||||
if (started)
|
||||
{
|
||||
successCount++;
|
||||
@@ -200,8 +210,10 @@ public sealed class RecoveryService
|
||||
|
||||
messages.Add(
|
||||
started
|
||||
? $"MP4 finalization resumed for task {item.RecordTaskId}."
|
||||
: $"MP4 finalization could not be resumed for task {item.RecordTaskId}.");
|
||||
? recoveredOrphan
|
||||
? $"任务 {item.RecordTaskId} 的遗留分片已恢复。"
|
||||
: $"任务 {item.RecordTaskId} 已恢复 MP4 转码。"
|
||||
: $"任务 {item.RecordTaskId} 无法恢复 MP4 转码。");
|
||||
}
|
||||
|
||||
return new RecoveryActionResultDto
|
||||
@@ -290,16 +302,6 @@ public sealed class RecoveryService
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recordTask.Status == RecordTaskStatus.Processing)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recordTask.Status is RecordTaskStatus.Starting or RecordTaskStatus.Running or RecordTaskStatus.Stopping)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recordTask.RecordSession.Status is RecordSessionStatus.Starting or RecordSessionStatus.Running or RecordSessionStatus.Stopping)
|
||||
{
|
||||
return false;
|
||||
@@ -310,8 +312,9 @@ public sealed class RecoveryService
|
||||
return false;
|
||||
}
|
||||
|
||||
var manifestSources = FfmpegService.ReadRecorderSegmentsManifest(recordTask.OutputFilePath);
|
||||
var recorderOutputPath = ResolveManualFinalizeSourcePath(recordTask, recordTask.RecordSession);
|
||||
if (!File.Exists(recorderOutputPath))
|
||||
if (manifestSources.Count == 0 && !File.Exists(recorderOutputPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -330,16 +333,21 @@ public sealed class RecoveryService
|
||||
if (recordTask.Status == RecordTaskStatus.Processing)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(recordTask.ErrorMessage)
|
||||
? "MP4 finalization is queued or paused and can be resumed."
|
||||
? "MP4 转码正在排队或已暂停,可以继续恢复。"
|
||||
: recordTask.ErrorMessage!;
|
||||
}
|
||||
|
||||
if (recordTask.Status is RecordTaskStatus.Starting or RecordTaskStatus.Running or RecordTaskStatus.Stopping)
|
||||
{
|
||||
return "父会话已经结束,但该分片仍显示为录制中;可安全对账并恢复转码、上传。";
|
||||
}
|
||||
|
||||
if (recordTask.Status == RecordTaskStatus.Completed)
|
||||
{
|
||||
return "The final MP4 output is missing, but the intermediate recording file is still available.";
|
||||
return "最终 MP4 文件缺失,但中间录制文件仍然存在。";
|
||||
}
|
||||
|
||||
return "Manual MP4 finalization can be retried from the intermediate recording file.";
|
||||
return "可以使用保留的中间录制文件重新执行 MP4 转码。";
|
||||
}
|
||||
|
||||
private static string ResolveManualFinalizeSourcePath(RecordTask recordTask, RecordSession recordSession)
|
||||
|
||||
Reference in New Issue
Block a user