show progress

This commit is contained in:
Michal Vrba
2025-12-14 16:33:34 +01:00
parent bf85ef29f8
commit dec69eb937
2 changed files with 776 additions and 31 deletions
+222 -31
View File
@@ -41,12 +41,12 @@
disabled="@isProcessing" />
<button @onclick="DownloadMp3"
@onmousedown="PreventDefault"
class="download-btn"
class="download-btn @(isProcessing ? "processing" : "")"
disabled="@(isProcessing)">
<span class="btn-icon">🎵</span>
@if (isProcessing)
{
<span>Processing...</span>
<span>Processing... (@progressPercentage%)</span>
}
else
{
@@ -55,6 +55,65 @@
</button>
</div>
@* Enhanced Progress Section - Show immediately when processing starts *@
@if (isProcessing)
{
<div class="enhanced-progress-section">
<div class="progress-header">
<h3 class="progress-title">
<span class="progress-icon @GetProgressIconClass()">@GetProgressIcon()</span>
@GetProgressStageTitle()
</h3>
<div class="progress-stats">
<span class="progress-percentage">@progressPercentage%</span>
<span class="progress-elapsed">⏱️ @GetElapsedTime()</span>
@if (estimatedTimeRemaining.HasValue && estimatedTimeRemaining.Value > TimeSpan.Zero)
{
<span class="progress-eta">ETA: @GetEstimatedTimeRemaining()</span>
}
</div>
</div>
<div class="progress-bar-container">
<div class="progress-bar-background">
<div class="progress-bar-fill" style="width: @(progressPercentage)%">
<div class="progress-bar-shimmer"></div>
</div>
</div>
<div class="progress-stages">
@foreach (var stage in progressStages)
{
<div class="progress-stage @GetStageClass(stage)">
<div class="stage-icon">@stage.Icon</div>
<div class="stage-label">@stage.Label</div>
</div>
}
</div>
</div>
<div class="progress-details">
<div class="progress-message">
<span class="message-text">@progressMessage</span>
@if (progressPercentage > 0 && progressPercentage < 100)
{
<div class="progress-dots">
<span class="dot">.</span>
<span class="dot">.</span>
<span class="dot">.</span>
</div>
}
</div>
@if (currentStageDetails != null)
{
<div class="stage-details">
<small>@currentStageDetails</small>
</div>
}
</div>
</div>
}
@* Video Information Section - Show as soon as we have details *@
@if (currentVideoDetails != null)
{
@@ -69,6 +128,7 @@
{
<div class="processing-overlay">
<div class="processing-spinner">🎵</div>
<div class="processing-text">@progressPercentage%</div>
</div>
}
}
@@ -80,29 +140,10 @@
</div>
}
@if (isProcessing)
{
<div class="progress-container @(currentVideoDetails != null ? "with-video-details" : "")">
<div class="progress-bar">
<div class="progress-fill" style="width: @(progressPercentage)%"></div>
</div>
<div class="progress-text">
@if (currentVideoDetails != null && progressPercentage > 15)
{
<span class="progress-highlight">@progressMessage</span>
<span class="progress-percentage">(@progressPercentage%)</span>
}
else
{
<span>@progressMessage (@progressPercentage%)</span>
}
</div>
</div>
}
@if (!string.IsNullOrWhiteSpace(outputMessage) && !isProcessing)
{
<div class="output-message">
<div class="output-message success">
<div class="success-icon">✅</div>
<p>@outputMessage</p>
</div>
}
@@ -111,7 +152,8 @@
@if (!string.IsNullOrWhiteSpace(apiErrorMessage) && !isProcessing)
{
<div class="error-message">
<p>❌ @apiErrorMessage</p>
<div class="error-icon">❌</div>
<p>@apiErrorMessage</p>
</div>
}
</div>
@@ -132,6 +174,7 @@
@if (isLoadingZip)
{
<span>Preparing ZIP...</span>
<div class="loading-spinner">⏳</div>
}
else
{
@@ -198,10 +241,17 @@
private bool isLoadingZip = false;
private int progressPercentage = 0;
private string progressMessage = "";
private string? currentStageDetails = null;
private List<DownloadItem> downloadLinks = new();
private YouTubeVideoDetails? currentVideoDetails = null;
private string currentSessionId = "";
private SessionZipInfo? sessionZipInfo = null;
// Enhanced progress tracking
private DateTime processingStartTime;
private TimeSpan? estimatedTimeRemaining;
private List<ProgressStage> progressStages = new();
private string currentProgressStage = "initializing";
public class DownloadItem
{
@@ -211,8 +261,21 @@
public DateTime CreatedAt { get; set; }
}
public class ProgressStage
{
public string Id { get; set; } = "";
public string Label { get; set; } = "";
public string Icon { get; set; } = "";
public bool IsCompleted { get; set; }
public bool IsActive { get; set; }
public int MinProgress { get; set; }
public int MaxProgress { get; set; }
}
protected override async Task OnInitializedAsync()
{
InitializeProgressStages();
try
{
// Initialize session on component load
@@ -231,6 +294,109 @@
}
}
private void InitializeProgressStages()
{
progressStages = new List<ProgressStage>
{
new() { Id = "initializing", Label = "Initializing", Icon = "🚀", MinProgress = 0, MaxProgress = 15 },
new() { Id = "fetching", Label = "Fetching Video", Icon = "📡", MinProgress = 15, MaxProgress = 35 },
new() { Id = "extracting", Label = "Extracting Audio", Icon = "🔄", MinProgress = 35, MaxProgress = 70 },
new() { Id = "converting", Label = "Converting to MP3", Icon = "🎵", MinProgress = 70, MaxProgress = 90 },
new() { Id = "finalizing", Label = "Finalizing", Icon = "✨", MinProgress = 90, MaxProgress = 100 }
};
}
private void UpdateProgressStage(int percentage)
{
foreach (var stage in progressStages)
{
stage.IsCompleted = percentage > stage.MaxProgress;
stage.IsActive = percentage >= stage.MinProgress && percentage <= stage.MaxProgress;
}
var activeStage = progressStages.FirstOrDefault(s => s.IsActive);
if (activeStage != null && currentProgressStage != activeStage.Id)
{
currentProgressStage = activeStage.Id;
UpdateStageDetails(activeStage.Id, percentage);
}
}
private void UpdateStageDetails(string stageId, int percentage)
{
currentStageDetails = stageId switch
{
"initializing" => "Preparing download request and validating URL...",
"fetching" => $"Analyzing video content and metadata... ({percentage}%)",
"extracting" => $"Extracting high-quality audio stream... ({percentage}%)",
"converting" => $"Converting to MP3 format with optimal settings... ({percentage}%)",
"finalizing" => $"Completing conversion and preparing download... ({percentage}%)",
_ => null
};
}
private string GetProgressStageTitle()
{
var activeStage = progressStages.FirstOrDefault(s => s.IsActive);
return activeStage?.Label ?? "Processing";
}
private string GetProgressIcon()
{
var activeStage = progressStages.FirstOrDefault(s => s.IsActive);
return activeStage?.Icon ?? "🎵";
}
private string GetProgressIconClass()
{
if (progressPercentage < 25) return "spinning";
if (progressPercentage < 50) return "pulse";
if (progressPercentage < 75) return "bounce";
if (progressPercentage < 100) return "glow";
return "success";
}
private string GetStageClass(ProgressStage stage)
{
if (stage.IsCompleted) return "completed";
if (stage.IsActive) return "active";
return "pending";
}
private string GetElapsedTime()
{
var elapsed = DateTime.Now - processingStartTime;
return elapsed.TotalMinutes >= 1
? $"{elapsed.Minutes:D2}:{elapsed.Seconds:D2}"
: $"{elapsed.Seconds}s";
}
private string GetEstimatedTimeRemaining()
{
if (!estimatedTimeRemaining.HasValue) return "";
var eta = estimatedTimeRemaining.Value;
return eta.TotalMinutes >= 1
? $"{eta.Minutes:D2}:{eta.Seconds:D2}"
: $"{eta.Seconds}s";
}
private void CalculateEstimatedTime(int currentProgress)
{
if (currentProgress > 5) // Only estimate after some progress
{
var elapsed = DateTime.Now - processingStartTime;
var progressRate = currentProgress / elapsed.TotalSeconds;
var remainingProgress = 100 - currentProgress;
var estimatedSeconds = remainingProgress / progressRate;
if (estimatedSeconds > 0 && estimatedSeconds < 300) // Cap at 5 minutes
{
estimatedTimeRemaining = TimeSpan.FromSeconds(estimatedSeconds);
}
}
}
private async Task LoadSessionZipInfoAsync()
{
try
@@ -292,6 +458,17 @@
outputMessage = "";
apiErrorMessage = "";
currentVideoDetails = null;
processingStartTime = DateTime.Now;
estimatedTimeRemaining = null;
currentStageDetails = null;
// Reset progress stages
foreach (var stage in progressStages)
{
stage.IsCompleted = false;
stage.IsActive = false;
}
progressStages.First().IsActive = true;
try
{
@@ -304,6 +481,7 @@
// Step 1: Start processing (which includes video details fetching)
progressMessage = "Starting video processing...";
progressPercentage = 5;
UpdateProgressStage(progressPercentage);
StateHasChanged();
var request = new ProcessVideoRequest
@@ -357,6 +535,7 @@
bool videoDetailsReceived = false;
int maxAttempts = 60; // Maximum polling attempts (about 2 minutes)
int attemptCount = 0;
int lastProgress = 0;
while (attemptCount < maxAttempts && isProcessing)
{
@@ -364,8 +543,15 @@
if (status != null)
{
// Update progress
progressPercentage = status.PercentageComplete;
// Update progress only if it has increased (prevent UI flicker)
if (status.PercentageComplete > lastProgress)
{
progressPercentage = status.PercentageComplete;
lastProgress = progressPercentage;
UpdateProgressStage(progressPercentage);
CalculateEstimatedTime(progressPercentage);
}
progressMessage = status.Message ?? "Processing...";
// Check if we received video details
@@ -428,6 +614,11 @@
{
try
{
// Ensure we reach 100% completion
progressPercentage = 100;
progressMessage = "Complete!";
UpdateProgressStage(progressPercentage);
// Use the video details from the result if we don't have them yet
if (currentVideoDetails == null && result.VideoDetails != null)
{
@@ -458,15 +649,15 @@
CreatedAt = DateTime.Now
});
outputMessage = $"✅ Successfully converted: {title}";
progressPercentage = 100;
progressMessage = "Complete!";
var processingTime = DateTime.Now - processingStartTime;
outputMessage = $"Successfully converted: {title} (took {processingTime.TotalSeconds:F1}s)";
// Reload session ZIP info after successful download
await LoadSessionZipInfoAsync();
// Small delay to show completion
await Task.Delay(500);
// Show completion for a moment
StateHasChanged();
await Task.Delay(1000);
isProcessing = false;
youtubeUrl = "";