Player Client and Visual Enhancements
- Redesigned audio player bar to be mobile-friendly - Added unloading for track switching (needs to be fixed) - Added IsLoading status so loading spinner isn't hanging around when it shouldn't be - Normalized styles with scoped files (will further reduce) - Layout Cleanup - EF fixes (migrations now function for deployment) - deploy script updates (new dedicated host)
This commit is contained in:
@@ -1,87 +0,0 @@
|
|||||||
@if (_isMinimized)
|
|
||||||
{
|
|
||||||
<div class="deepdrft-minimized-player-dock">
|
|
||||||
<MudIconButton Icon="@GetPlayIcon()"
|
|
||||||
Color="Color.Primary"
|
|
||||||
Size="Size.Large"
|
|
||||||
Class="deepdrft-minimized-player-button"
|
|
||||||
OnClick="@ToggleMinimized" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@* Full-width outer container *@
|
|
||||||
<div class="deepdrft-player-outer-container">
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="deepdrft-player-inner-container">
|
|
||||||
<div class="deepdrft-audio-player-bar">
|
|
||||||
<div class="deepdrft-audio-player-content">
|
|
||||||
|
|
||||||
@* Controls section *@
|
|
||||||
<div class="deepdrft-audio-controls-section">
|
|
||||||
<div class="deepdrft-audio-buttons-row">
|
|
||||||
<MudIconButton Icon="@GetPlayIcon()"
|
|
||||||
Color="Color.Primary"
|
|
||||||
Size="Size.Large"
|
|
||||||
OnClick="@TogglePlayPause"
|
|
||||||
Disabled="!IsLoaded"/>
|
|
||||||
@if (IsLoaded)
|
|
||||||
{
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Stop"
|
|
||||||
Color="Color.Primary"
|
|
||||||
OnClick="@Stop"/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="deepdrft-audio-info-row">
|
|
||||||
<MudText Typo="Typo.body2" Class="font-monospace deepdrft-audio-time">
|
|
||||||
@FormatTime(CurrentTime) / @(Duration.HasValue ? FormatTime(Duration.Value) : "--:--")
|
|
||||||
</MudText>
|
|
||||||
@if (!IsLoaded)
|
|
||||||
{
|
|
||||||
<MudProgressCircular Color="Color.Tertiary" Value="@LoadProgress" Size="Size.Small"/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Seek slider *@
|
|
||||||
<MudSlider T="double"
|
|
||||||
Min="0"
|
|
||||||
Max="@(Duration ?? 0D)"
|
|
||||||
Step="0.1"
|
|
||||||
Value="@CurrentTime"
|
|
||||||
ValueChanged="@OnSeek"
|
|
||||||
Disabled="!IsLoaded"
|
|
||||||
Class="deepdrft-audio-seek-slider"/>
|
|
||||||
|
|
||||||
@* Volume section *@
|
|
||||||
<div class="deepdrft-audio-volume-section">
|
|
||||||
<MudIcon Icon="@GetVolumeIcon()" Class="deepdrft-audio-volume-icon"/>
|
|
||||||
<MudSlider T="double"
|
|
||||||
Min="0"
|
|
||||||
Max="1"
|
|
||||||
Step="0.01"
|
|
||||||
Value="@Volume"
|
|
||||||
ValueChanged="@OnVolumeChange"
|
|
||||||
Class="deepdrft-audio-volume-slider"/>
|
|
||||||
</div>
|
|
||||||
<div class="deepdrft-audio-minimize-section">
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Minimize"
|
|
||||||
Color="Color.Secondary"
|
|
||||||
Size="Size.Small"
|
|
||||||
OnClick="@ToggleMinimized"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(ErrorMessage))
|
|
||||||
{
|
|
||||||
<MudAlert Severity="Severity.Error" ShowCloseIcon="true" CloseIconClicked="ClearError" Class="ma-2">
|
|
||||||
@ErrorMessage
|
|
||||||
</MudAlert>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</MudContainer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Spacer div to maintain layout spacing *@
|
|
||||||
<div class="deepdrft-player-spacer"></div>
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
@if (_isMinimized)
|
||||||
|
{
|
||||||
|
<div class="deepdrft-minimized-player-dock">
|
||||||
|
<MudIconButton Icon="@GetPlayIcon()"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
Class="deepdrft-minimized-player-button"
|
||||||
|
OnClick="@ToggleMinimized" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@* Full-width outer container *@
|
||||||
|
<div class="deepdrft-player-outer-container">
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large" Class="deepdrft-player-inner-container">
|
||||||
|
<div class="deepdrft-audio-player-bar">
|
||||||
|
@* Full Screen *@
|
||||||
|
<div class="d-none d-md-block deepdrft-audio-player-content">
|
||||||
|
<div class="deepdrft-audio-controls-section">
|
||||||
|
<div class="deepdrft-audio-buttons-row">
|
||||||
|
<PlayerControls IsPlaying="IsPlaying"
|
||||||
|
IsLoaded="IsLoaded"
|
||||||
|
TogglePlayPause="@TogglePlayPause"
|
||||||
|
Stop="@Stop"/>
|
||||||
|
|
||||||
|
@if (!IsLoaded)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Color="Color.Tertiary"
|
||||||
|
Max="1D"
|
||||||
|
Value="@LoadProgress"
|
||||||
|
Indeterminate="@(LoadProgress == 0)"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<TimestampLabel CurrentTime="CurrentTime"
|
||||||
|
Duration="Duration"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Seek slider *@
|
||||||
|
<MudSlider T="double"
|
||||||
|
Min="0"
|
||||||
|
Max="@(Duration ?? 0D)"
|
||||||
|
Step="0.1"
|
||||||
|
Value="@CurrentTime"
|
||||||
|
ValueChanged="@OnSeek"
|
||||||
|
Disabled="!IsLoaded"
|
||||||
|
Class="deepdrft-audio-seek-slider"/>
|
||||||
|
|
||||||
|
<div class="deepdrft-audio-volume-section">
|
||||||
|
<VolumeControls Volume="@Volume" VolumeChanged="@OnVolumeChange"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Mobile *@
|
||||||
|
<div class="d-md-none deepdrft-audio-player-content">
|
||||||
|
<div class="deepdrft-audio-controls-section">
|
||||||
|
<div class="deepdrft-audio-buttons-row">
|
||||||
|
<PlayerControls IsPlaying="IsPlaying"
|
||||||
|
IsLoaded="IsLoaded"
|
||||||
|
TogglePlayPause="@TogglePlayPause"
|
||||||
|
Stop="@Stop"/>
|
||||||
|
|
||||||
|
<VolumeControls Volume="@Volume"
|
||||||
|
VolumeChanged="@OnVolumeChange"/>
|
||||||
|
|
||||||
|
@if (!IsLoaded)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Color="Color.Tertiary"
|
||||||
|
Max="1D"
|
||||||
|
Value="@LoadProgress"
|
||||||
|
Indeterminate="@(LoadProgress == 0)"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<TimestampLabel CurrentTime="CurrentTime"
|
||||||
|
Duration="Duration"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Seek slider *@
|
||||||
|
<MudSlider T="double"
|
||||||
|
Min="0"
|
||||||
|
Max="@(Duration ?? 0D)"
|
||||||
|
Step="0.1"
|
||||||
|
Value="@CurrentTime"
|
||||||
|
ValueChanged="@OnSeek"
|
||||||
|
Disabled="!IsLoaded"
|
||||||
|
Class="deepdrft-audio-seek-slider"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="deepdrft-audio-minimize-section">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Minimize"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="@ToggleMinimized"/>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Close"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="@Close"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(ErrorMessage))
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error" ShowCloseIcon="true" CloseIconClicked="ClearError" Class="ma-2">
|
||||||
|
@ErrorMessage
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
</MudContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Spacer div to maintain layout spacing *@
|
||||||
|
<div class="deepdrft-player-spacer"></div>
|
||||||
|
}
|
||||||
+26
-14
@@ -1,10 +1,8 @@
|
|||||||
using DeepDrftModels.Entities;
|
using DeepDrftWeb.Client.Services;
|
||||||
using DeepDrftWeb.Client.Clients;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using DeepDrftWeb.Client.Services;
|
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
|
||||||
namespace DeepDrftWeb.Client.Controls;
|
namespace DeepDrftWeb.Client.Controls.AudioPlayerBar;
|
||||||
|
|
||||||
public partial class AudioPlayerBar : ComponentBase
|
public partial class AudioPlayerBar : ComponentBase
|
||||||
{
|
{
|
||||||
@@ -24,22 +22,18 @@ public partial class AudioPlayerBar : ComponentBase
|
|||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
|
|
||||||
PlayerService.OnStateChanged += StateHasChanged;
|
PlayerService.OnStateChanged += StateHasChanged;
|
||||||
|
PlayerService.OnTrackSelected += Expand;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPlayIcon()
|
private async Task Expand()
|
||||||
{
|
{
|
||||||
return IsPlaying ? Icons.Material.Filled.Pause : Icons.Material.Filled.PlayArrow;
|
if (_isMinimized)
|
||||||
}
|
|
||||||
|
|
||||||
private string GetVolumeIcon()
|
|
||||||
{
|
{
|
||||||
if (Volume == 0) return Icons.Material.Filled.VolumeOff;
|
_isMinimized = false;
|
||||||
if (Volume < 0.5) return Icons.Material.Filled.VolumeDown;
|
StateHasChanged();
|
||||||
return Icons.Material.Filled.VolumeUp;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatTime(double seconds)
|
private static string FormatTime(double seconds)
|
||||||
{
|
{
|
||||||
var timeSpan = TimeSpan.FromSeconds(seconds);
|
var timeSpan = TimeSpan.FromSeconds(seconds);
|
||||||
@@ -77,4 +71,22 @@ public partial class AudioPlayerBar : ComponentBase
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Close()
|
||||||
|
{
|
||||||
|
if (PlayerService.IsLoaded)
|
||||||
|
{
|
||||||
|
await PlayerService.Unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_isMinimized)
|
||||||
|
{
|
||||||
|
_isMinimized = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPlayIcon()
|
||||||
|
{
|
||||||
|
return IsPlaying ? Icons.Material.Filled.Pause : Icons.Material.Filled.PlayArrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+13
-11
@@ -22,14 +22,15 @@
|
|||||||
/* Player bar with rounded corners and semi-opaque background */
|
/* Player bar with rounded corners and semi-opaque background */
|
||||||
.deepdrft-audio-player-bar {
|
.deepdrft-audio-player-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: rgba(var(--mud-palette-surface-rgb), 0.75);
|
background: var(--deepdrft-theme-background-gray);
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(15px);
|
||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(15px);
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
border: 1px solid rgba(var(--mud-palette-divider-rgb), 0.9);
|
border: 1px solid var(--deepdrft-theme-primary);
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4),
|
||||||
|
0 2px 10px var(--deepdrft-theme-secondary);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
color: var(--mud-palette-text-primary);
|
color: #ffffff;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@@ -54,13 +55,13 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: linear-gradient(135deg,
|
background: linear-gradient(135deg,
|
||||||
rgba(var(--mud-palette-primary-rgb), 0.95) 0%,
|
var(--deepdrft-theme-primary) 0%,
|
||||||
rgba(var(--mud-palette-secondary-rgb), 0.9) 50%,
|
var(--deepdrft-theme-secondary) 50%,
|
||||||
rgba(var(--mud-palette-tertiary-rgb), 0.95) 100%
|
var(--deepdrft-theme-tertiary) 100%
|
||||||
);
|
);
|
||||||
backdrop-filter: blur(15px);
|
backdrop-filter: blur(15px);
|
||||||
border: 2px solid rgba(var(--mud-palette-secondary-rgb), 0.7);
|
border: 2px solid var(--deepdrft-theme-secondary);
|
||||||
box-shadow: 0 4px 20px rgba(var(--mud-palette-primary-rgb), 0.6),
|
box-shadow: 0 4px 20px var(--deepdrft-theme-primary),
|
||||||
0 2px 10px rgba(0, 0, 0, 0.3);
|
0 2px 10px rgba(0, 0, 0, 0.3);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -109,6 +110,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
|
justify-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deepdrft-audio-volume-section {
|
.deepdrft-audio-volume-section {
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
@if (_isMinimized)
|
||||||
|
{
|
||||||
|
<div class="minimized-dock d-flex align-center justify-center"
|
||||||
|
@onclick="@ToggleMinimized">
|
||||||
|
<MudIconButton Icon="@GetPlayIcon()"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
Class="minimized-button"
|
||||||
|
OnClick="@ToggleMinimized"/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="player-outer-container d-flex flex-column">
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large" Class="player-inner-container">
|
||||||
|
<div class="player-backdrop pa-3">
|
||||||
|
|
||||||
|
@* Desktop Layout *@
|
||||||
|
<div class="d-none d-md-flex align-center gap-3">
|
||||||
|
<div class="controls-left d-flex flex-column align-center gap-2">
|
||||||
|
<div class="d-flex align-center gap-1">
|
||||||
|
<PlayerControls IsPlaying="IsPlaying"
|
||||||
|
IsLoaded="IsLoaded"
|
||||||
|
TogglePlayPause="@TogglePlayPause"
|
||||||
|
Stop="@Stop"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<TimestampLabel CurrentTime="CurrentTime" Duration="Duration"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="seekbar-flex mx-3">
|
||||||
|
<MudSlider T="double"
|
||||||
|
Min="0"
|
||||||
|
Max="@(Duration ?? 0D)"
|
||||||
|
Step="0.1"
|
||||||
|
Value="@CurrentTime"
|
||||||
|
ValueChanged="@OnSeek"
|
||||||
|
Disabled="!IsLoaded"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="volume-right">
|
||||||
|
<VolumeControls Volume="@Volume" VolumeChanged="@OnVolumeChange"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Mobile Layout *@
|
||||||
|
<div class="d-md-none">
|
||||||
|
<div class="d-flex align-center justify-space-between mb-3">
|
||||||
|
<div class="d-flex align-center gap-2">
|
||||||
|
<PlayerControls IsPlaying="IsPlaying"
|
||||||
|
IsLoaded="IsLoaded"
|
||||||
|
TogglePlayPause="@TogglePlayPause"
|
||||||
|
Stop="@Stop"/>
|
||||||
|
@if (IsLoading)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Color="Color.Tertiary"
|
||||||
|
Size="Size.Small"
|
||||||
|
Max="1D"
|
||||||
|
Value="@LoadProgress"
|
||||||
|
Indeterminate="@(LoadProgress == 0)"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<TimestampLabel CurrentTime="CurrentTime" Duration="Duration"/>
|
||||||
|
<VolumeControls Volume="@Volume" VolumeChanged="@OnVolumeChange"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MudSlider T="double"
|
||||||
|
Min="0"
|
||||||
|
Max="@(Duration ?? 0D)"
|
||||||
|
Step="0.1"
|
||||||
|
Value="@CurrentTime"
|
||||||
|
ValueChanged="@OnSeek"
|
||||||
|
Disabled="!IsLoaded"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Control Buttons - positioned absolutely like original *@
|
||||||
|
<div class="player-controls d-flex align-center justify-center gap-1">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Minimize"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="@ToggleMinimized"/>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Close"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
Size="Size.Small"
|
||||||
|
OnClick="@Close"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(ErrorMessage))
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error"
|
||||||
|
ShowCloseIcon="true"
|
||||||
|
CloseIconClicked="ClearError"
|
||||||
|
Class="ma-2">
|
||||||
|
@ErrorMessage
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Spacer to prevent content overlap *@
|
||||||
|
<div class="player-spacer"></div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using DeepDrftWeb.Client.Services;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace DeepDrftWeb.Client.Controls.AudioPlayerBar;
|
||||||
|
|
||||||
|
public partial class AudioPlayerBar2 : ComponentBase
|
||||||
|
{
|
||||||
|
[CascadingParameter] public required IPlayerService PlayerService { get; set; }
|
||||||
|
[Parameter] public bool ShowLoadProgress { get; set; } = true;
|
||||||
|
private bool _isMinimized = true;
|
||||||
|
|
||||||
|
private bool IsLoaded => PlayerService.IsLoaded;
|
||||||
|
private bool IsLoading => PlayerService.IsLoading;
|
||||||
|
private bool IsPlaying => PlayerService.IsPlaying;
|
||||||
|
private bool IsPaused => PlayerService.IsPaused;
|
||||||
|
private double CurrentTime => PlayerService.CurrentTime;
|
||||||
|
private double? Duration => PlayerService.Duration;
|
||||||
|
private double Volume => PlayerService.Volume;
|
||||||
|
private double LoadProgress => PlayerService.LoadProgress;
|
||||||
|
private string? ErrorMessage => PlayerService.ErrorMessage;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await base.OnInitializedAsync();
|
||||||
|
PlayerService.OnStateChanged += StateHasChanged;
|
||||||
|
PlayerService.OnTrackSelected += Expand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Expand()
|
||||||
|
{
|
||||||
|
if (_isMinimized)
|
||||||
|
{
|
||||||
|
_isMinimized = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static string FormatTime(double seconds)
|
||||||
|
{
|
||||||
|
var timeSpan = TimeSpan.FromSeconds(seconds);
|
||||||
|
return timeSpan.ToString(timeSpan.TotalHours >= 1 ? @"h\:mm\:ss" : @"m\:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TogglePlayPause()
|
||||||
|
{
|
||||||
|
await PlayerService.TogglePlayPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Stop()
|
||||||
|
{
|
||||||
|
await PlayerService.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSeek(double position)
|
||||||
|
{
|
||||||
|
await PlayerService.Seek(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnVolumeChange(double volume)
|
||||||
|
{
|
||||||
|
await PlayerService.SetVolume(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearError()
|
||||||
|
{
|
||||||
|
PlayerService.ClearError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleMinimized()
|
||||||
|
{
|
||||||
|
_isMinimized = !_isMinimized;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Close()
|
||||||
|
{
|
||||||
|
if (PlayerService.IsLoaded)
|
||||||
|
{
|
||||||
|
await PlayerService.Unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_isMinimized)
|
||||||
|
{
|
||||||
|
_isMinimized = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPlayIcon()
|
||||||
|
{
|
||||||
|
return IsPlaying ? Icons.Material.Filled.Pause : Icons.Material.Filled.PlayArrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/* Preserve key visual styles while simplifying layout */
|
||||||
|
|
||||||
|
/* Player outer container - fixed positioning */
|
||||||
|
.player-outer-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1200;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Player inner container */
|
||||||
|
.player-inner-container {
|
||||||
|
padding: 1rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom backdrop blur container */
|
||||||
|
.player-backdrop {
|
||||||
|
position: relative;
|
||||||
|
background: var(--deepdrft-theme-background-gray);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px);
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: 1px solid var(--deepdrft-theme-primary);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4),
|
||||||
|
0 2px 10px var(--deepdrft-theme-secondary);
|
||||||
|
color: #ffffff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Control buttons positioning */
|
||||||
|
.player-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Minimized floating dock with gradient */
|
||||||
|
.minimized-dock {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 60px;
|
||||||
|
right: 60px;
|
||||||
|
z-index: 1300;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
background: linear-gradient(135deg,
|
||||||
|
var(--deepdrft-theme-primary) 0%,
|
||||||
|
var(--deepdrft-theme-secondary) 50%,
|
||||||
|
var(--deepdrft-theme-tertiary) 100%
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px);
|
||||||
|
border: 2px solid var(--deepdrft-theme-secondary);
|
||||||
|
box-shadow: 0 4px 20px var(--deepdrft-theme-primary),
|
||||||
|
0 2px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimized-dock:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 6px 25px rgba(var(--deepdrft-theme-primary), 0.8),
|
||||||
|
0 3px 15px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Minimized button styles */
|
||||||
|
.minimized-button {
|
||||||
|
border-radius: 50% !important;
|
||||||
|
background: transparent !important;
|
||||||
|
color: white !important;
|
||||||
|
transition: all 0.3s ease !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: none !important;
|
||||||
|
width: 48px !important;
|
||||||
|
height: 48px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spacer to prevent content overlap */
|
||||||
|
.player-spacer {
|
||||||
|
height: 140px;
|
||||||
|
width: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Essential layout adjustments only */
|
||||||
|
.controls-left {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seekbar-flex {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-right {
|
||||||
|
/*min-width: 140px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.minimized-dock {
|
||||||
|
bottom: 15px;
|
||||||
|
right: 15px;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimized-button {
|
||||||
|
width: 44px !important;
|
||||||
|
height: 44px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-inner-container {
|
||||||
|
padding: 0.75rem;
|
||||||
|
padding-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-backdrop {
|
||||||
|
border-radius: 1rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-spacer {
|
||||||
|
height: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<div class="player-buttons">
|
||||||
|
<MudIconButton Icon="@GetPlayIcon()"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
OnClick="@TogglePlayPause"
|
||||||
|
Disabled="!IsLoaded"/>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Stop"
|
||||||
|
Color="Color.Primary"
|
||||||
|
Size="Size.Large"
|
||||||
|
OnClick="@Stop"
|
||||||
|
Disabled="!IsLoaded"/>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace DeepDrftWeb.Client.Controls.AudioPlayerBar;
|
||||||
|
|
||||||
|
public partial class PlayerControls : ComponentBase
|
||||||
|
{
|
||||||
|
[Parameter] public required bool IsPlaying { get; set; }
|
||||||
|
[Parameter] public required bool IsLoaded { get; set; }
|
||||||
|
[Parameter] public required EventCallback TogglePlayPause { get; set; }
|
||||||
|
[Parameter] public required EventCallback Stop { get; set; }
|
||||||
|
private string GetPlayIcon()
|
||||||
|
{
|
||||||
|
return IsPlaying ? Icons.Material.Filled.Pause : Icons.Material.Filled.PlayArrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/* PlayerControls Component Styles */
|
||||||
|
|
||||||
|
/* Button spacing and alignment */
|
||||||
|
.player-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="timestamp-display">
|
||||||
|
<MudText Typo="Typo.body2" Class="time-text">
|
||||||
|
@FormatTime(CurrentTime) / @(Duration.HasValue ? FormatTime(Duration.Value) : "--:--")
|
||||||
|
</MudText>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace DeepDrftWeb.Client.Controls.AudioPlayerBar;
|
||||||
|
|
||||||
|
public partial class TimestampLabel : ComponentBase
|
||||||
|
{
|
||||||
|
[Parameter] public required double CurrentTime { get; set; }
|
||||||
|
[Parameter] public required double? Duration { get; set; }
|
||||||
|
private static string FormatTime(double seconds)
|
||||||
|
{
|
||||||
|
var timeSpan = TimeSpan.FromSeconds(seconds);
|
||||||
|
return timeSpan.ToString(timeSpan.TotalHours >= 1 ? @"h\:mm\:ss" : @"m\:ss");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* TimestampLabel Component Styles */
|
||||||
|
|
||||||
|
/* Timestamp display */
|
||||||
|
.timestamp-display {
|
||||||
|
min-width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Time text styling */
|
||||||
|
.time-text {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="volume-controls">
|
||||||
|
<MudIcon Icon="@GetVolumeIcon()" Class="volume-icon"/>
|
||||||
|
<MudSlider T="double"
|
||||||
|
Min="0"
|
||||||
|
Max="1"
|
||||||
|
Step="0.01"
|
||||||
|
Value="@Volume"
|
||||||
|
ValueChanged="@VolumeChanged"
|
||||||
|
Class="volume-slider"/>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using MudBlazor;
|
||||||
|
|
||||||
|
namespace DeepDrftWeb.Client.Controls.AudioPlayerBar;
|
||||||
|
|
||||||
|
public partial class VolumeControls : ComponentBase
|
||||||
|
{
|
||||||
|
[Parameter] public required double Volume { get; set; }
|
||||||
|
[Parameter] public required EventCallback<double> VolumeChanged { get; set; }
|
||||||
|
private string GetVolumeIcon()
|
||||||
|
{
|
||||||
|
if (Volume == 0) return Icons.Material.Filled.VolumeOff;
|
||||||
|
if (Volume < 0.5) return Icons.Material.Filled.VolumeDown;
|
||||||
|
return Icons.Material.Filled.VolumeUp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/* VolumeControls Component Styles */
|
||||||
|
|
||||||
|
/* Volume control container */
|
||||||
|
.volume-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Volume icon styling */
|
||||||
|
.volume-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Volume slider styling */
|
||||||
|
.volume-slider {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@using DeepDrftWeb.Client.Controls
|
@using DeepDrftWeb.Client.Controls
|
||||||
|
@using DeepDrftWeb.Client.Controls.AudioPlayerBar
|
||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<MudThemeProvider Theme="@_themeManager.Theme" IsDarkMode="_isDarkMode" />
|
<MudThemeProvider Theme="@_themeManager.Theme" IsDarkMode="_isDarkMode" />
|
||||||
@@ -7,8 +8,6 @@
|
|||||||
<MudSnackbarProvider />
|
<MudSnackbarProvider />
|
||||||
<MudLayout>
|
<MudLayout>
|
||||||
<AudioPlayerService>
|
<AudioPlayerService>
|
||||||
@* <MudThemeManagerButton OnClick="@((e) => OpenThemeManager(true))" /> *@
|
|
||||||
@* <MudThemeManager Open="_themeManagerOpen" OpenChanged="OpenThemeManager" Theme="_themeManager" ThemeChanged="UpdateTheme" /> *@
|
|
||||||
<MudAppBar Elevation="_themeManager.AppBarElevation">
|
<MudAppBar Elevation="_themeManager.AppBarElevation">
|
||||||
<MudAvatar Class="mr-2">
|
<MudAvatar Class="mr-2">
|
||||||
<MudImage Src="img/deepdrft-logo.jpg"></MudImage>
|
<MudImage Src="img/deepdrft-logo.jpg"></MudImage>
|
||||||
@@ -16,13 +15,12 @@
|
|||||||
<NavMenu />
|
<NavMenu />
|
||||||
<MudSpacer/>
|
<MudSpacer/>
|
||||||
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle"/>
|
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle"/>
|
||||||
@* <MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End"/> *@
|
|
||||||
</MudAppBar>
|
</MudAppBar>
|
||||||
<MudMainContent Class="pt-16 deepdrft-layout-with-overlay-player">
|
<MudMainContent Class="pt-16 deepdrft-layout-with-overlay-player">
|
||||||
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4">
|
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4">
|
||||||
@Body
|
@Body
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
<AudioPlayerBar />
|
<AudioPlayerBar2 />
|
||||||
</MudMainContent>
|
</MudMainContent>
|
||||||
</AudioPlayerService>
|
</AudioPlayerService>
|
||||||
</MudLayout>
|
</MudLayout>
|
||||||
@@ -37,7 +35,6 @@
|
|||||||
@code {
|
@code {
|
||||||
private bool _drawerOpen = true;
|
private bool _drawerOpen = true;
|
||||||
private bool _isDarkMode = true;
|
private bool _isDarkMode = true;
|
||||||
// private MudTheme? _theme = null;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@page "/tracks"
|
@page "/tracks"
|
||||||
@rendermode @(new InteractiveAutoRenderMode(prerender: false))
|
|
||||||
|
|
||||||
@using DeepDrftWeb.Client.Controls
|
@using DeepDrftWeb.Client.Controls
|
||||||
|
|
||||||
@@ -20,21 +19,21 @@
|
|||||||
SelectedChanged="@SetPage"
|
SelectedChanged="@SetPage"
|
||||||
BoundaryCount="2"
|
BoundaryCount="2"
|
||||||
MiddleCount="3"/>
|
MiddleCount="3"/>
|
||||||
|
|
||||||
<div class="interactivity-test mt-4">
|
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="TestInteractivity">
|
|
||||||
Test Interactivity (@_clickCount)
|
|
||||||
</MudButton>
|
|
||||||
<MudText Typo="Typo.body2" Class="mt-2">
|
|
||||||
Lifecycle Status: @_lifecycleStatus
|
|
||||||
</MudText>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="tracks-content">
|
<div class="tracks-content">
|
||||||
<MudSkeleton Height="95%" Class="pa-2 ma-6"/>
|
<MudGrid Spacing="3">
|
||||||
|
@foreach (var i in Enumerable.Range(0, 12))
|
||||||
|
{
|
||||||
|
<MudItem xs="12" sm="6" md="4" lg="3" xl="3">
|
||||||
|
<div class="deepdrft-track-gallery-item-center">
|
||||||
|
<MudSkeleton Width="240px" Height="240px" SkeletonType="SkeletonType.Rectangle"/>
|
||||||
|
</div>
|
||||||
|
</MudItem>
|
||||||
|
}
|
||||||
|
</MudGrid>
|
||||||
</div>
|
</div>
|
||||||
<div class="tracks-footer">
|
<div class="tracks-footer">
|
||||||
<MudSkeleton Height="60px" Width="240px" Class="justify-center"/>
|
<MudSkeleton Height="60px" Width="240px" Class="justify-center"/>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public partial class TracksView : ComponentBase
|
|||||||
|
|
||||||
if (track is null)
|
if (track is null)
|
||||||
{
|
{
|
||||||
await PlayerService.Stop();
|
await PlayerService.Unload();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ public class AudioInteropService : IAsyncDisposable
|
|||||||
return await InvokeJsAsync<AudioOperationResult>("DeepDrftAudio.stop", playerId);
|
return await InvokeJsAsync<AudioOperationResult>("DeepDrftAudio.stop", playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<AudioOperationResult> UnloadAsync(string playerId)
|
||||||
|
{
|
||||||
|
return await InvokeJsAsync<AudioOperationResult>("DeepDrftAudio.unload", playerId);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<AudioOperationResult> SeekAsync(string playerId, double position)
|
public async Task<AudioOperationResult> SeekAsync(string playerId, double position)
|
||||||
{
|
{
|
||||||
return await InvokeJsAsync<AudioOperationResult>("DeepDrftAudio.seek", playerId, position);
|
return await InvokeJsAsync<AudioOperationResult>("DeepDrftAudio.seek", playerId, position);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace DeepDrftWeb.Client.Services;
|
|||||||
public class AudioPlaybackEngine : IAsyncDisposable
|
public class AudioPlaybackEngine : IAsyncDisposable
|
||||||
{
|
{
|
||||||
public event Events.EventAsync<double>? OnProgressChanged;
|
public event Events.EventAsync<double>? OnProgressChanged;
|
||||||
|
public event Events.EventAsync<double>? OnLoadChanged;
|
||||||
public event Events.EventAsync? OnPlaybackEnded;
|
public event Events.EventAsync? OnPlaybackEnded;
|
||||||
|
|
||||||
public required TrackMediaClient Client { get; set; }
|
public required TrackMediaClient Client { get; set; }
|
||||||
@@ -15,6 +16,7 @@ public class AudioPlaybackEngine : IAsyncDisposable
|
|||||||
public string PlayerId { get; private set; } = Guid.NewGuid().ToString();
|
public string PlayerId { get; private set; } = Guid.NewGuid().ToString();
|
||||||
public bool IsInitialized { get; private set; } = false;
|
public bool IsInitialized { get; private set; } = false;
|
||||||
public bool IsLoaded { get; private set; } = false;
|
public bool IsLoaded { get; private set; } = false;
|
||||||
|
public bool IsLoading { get; private set; } = false;
|
||||||
public bool IsPlaying { get; private set; } = false;
|
public bool IsPlaying { get; private set; } = false;
|
||||||
public bool IsPaused { get; private set; } = false;
|
public bool IsPaused { get; private set; } = false;
|
||||||
public double CurrentTime { get; private set; } = 0;
|
public double CurrentTime { get; private set; } = 0;
|
||||||
@@ -51,12 +53,25 @@ public class AudioPlaybackEngine : IAsyncDisposable
|
|||||||
|
|
||||||
public async Task LoadTrack(TrackEntity track)
|
public async Task LoadTrack(TrackEntity track)
|
||||||
{
|
{
|
||||||
if (IsLoaded) return;
|
TrackMediaResponse? audio = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Immediately reset state to indicate loading has started
|
||||||
ErrorMessage = null;
|
ErrorMessage = null;
|
||||||
LoadProgress = 0;
|
LoadProgress = 0;
|
||||||
|
IsLoaded = false;
|
||||||
|
IsLoading = true;
|
||||||
|
Duration = null;
|
||||||
|
CurrentTime = 0;
|
||||||
|
|
||||||
|
// Trigger load event immediately to show loading state in UI
|
||||||
|
if (OnLoadChanged != null) await OnLoadChanged.Invoke(0);
|
||||||
|
|
||||||
|
if (IsPlaying || IsPaused)
|
||||||
|
{
|
||||||
|
// If we were playing/paused, unload the current track
|
||||||
|
await Unload();
|
||||||
|
}
|
||||||
|
|
||||||
AudioOperationResult? loadResult = await AudioInterop.InitializeBufferedPlayerAsync(PlayerId);
|
AudioOperationResult? loadResult = await AudioInterop.InitializeBufferedPlayerAsync(PlayerId);
|
||||||
if (loadResult?.Success != true)
|
if (loadResult?.Success != true)
|
||||||
@@ -77,9 +92,7 @@ public class AudioPlaybackEngine : IAsyncDisposable
|
|||||||
ErrorMessage = "No audio returned from server";
|
ErrorMessage = "No audio returned from server";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
audio = mediaResult.Value;
|
||||||
TrackMediaResponse audio = mediaResult.Value;
|
|
||||||
await StreamAndPlay(audio);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -87,6 +100,21 @@ public class AudioPlaybackEngine : IAsyncDisposable
|
|||||||
LoadProgress = 0;
|
LoadProgress = 0;
|
||||||
IsLoaded = false;
|
IsLoaded = false;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (audio == null) return;
|
||||||
|
|
||||||
|
await StreamAndPlay(audio);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorMessage = $"Error streaming audio: {ex.Message}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StreamAndPlay(TrackMediaResponse audio)
|
private async Task StreamAndPlay(TrackMediaResponse audio)
|
||||||
@@ -124,6 +152,7 @@ public class AudioPlaybackEngine : IAsyncDisposable
|
|||||||
if (audio.ContentLength > 0)
|
if (audio.ContentLength > 0)
|
||||||
{
|
{
|
||||||
LoadProgress = Math.Min(1.0, (double)totalBytesRead / audio.ContentLength);
|
LoadProgress = Math.Min(1.0, (double)totalBytesRead / audio.ContentLength);
|
||||||
|
if (OnLoadChanged != null) await OnLoadChanged.Invoke(LoadProgress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (currentBytes > 0);
|
} while (currentBytes > 0);
|
||||||
@@ -140,6 +169,9 @@ public class AudioPlaybackEngine : IAsyncDisposable
|
|||||||
LoadProgress = 1.0;
|
LoadProgress = 1.0;
|
||||||
IsLoaded = true;
|
IsLoaded = true;
|
||||||
ErrorMessage = null;
|
ErrorMessage = null;
|
||||||
|
|
||||||
|
// Trigger final load completion event
|
||||||
|
if (OnLoadChanged != null) await OnLoadChanged.Invoke(1.0);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -217,6 +249,35 @@ public class AudioPlaybackEngine : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task Unload()
|
||||||
|
{
|
||||||
|
if (!IsLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Stop();
|
||||||
|
var result = await AudioInterop.UnloadAsync(PlayerId);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
IsPlaying = false;
|
||||||
|
IsPaused = false;
|
||||||
|
CurrentTime = 0;
|
||||||
|
Duration = null;
|
||||||
|
LoadProgress = 0;
|
||||||
|
IsLoaded = false;
|
||||||
|
ErrorMessage = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessage = $"Unload error: {result.Error}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorMessage = $"Error unloading track: {ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task OnSeek(double position)
|
public async Task OnSeek(double position)
|
||||||
{
|
{
|
||||||
if (!IsLoaded) return;
|
if (!IsLoaded) return;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using DeepDrftModels.Entities;
|
using DeepDrftModels.Entities;
|
||||||
|
using NetBlocks.Models;
|
||||||
|
|
||||||
namespace DeepDrftWeb.Client.Services;
|
namespace DeepDrftWeb.Client.Services;
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ public interface IPlayerService
|
|||||||
// State properties
|
// State properties
|
||||||
bool IsInitialized { get; }
|
bool IsInitialized { get; }
|
||||||
bool IsLoaded { get; }
|
bool IsLoaded { get; }
|
||||||
|
bool IsLoading { get; }
|
||||||
bool IsPlaying { get; }
|
bool IsPlaying { get; }
|
||||||
bool IsPaused { get; }
|
bool IsPaused { get; }
|
||||||
double CurrentTime { get; }
|
double CurrentTime { get; }
|
||||||
@@ -17,10 +19,12 @@ public interface IPlayerService
|
|||||||
|
|
||||||
// Events for UI updates
|
// Events for UI updates
|
||||||
event Action? OnStateChanged;
|
event Action? OnStateChanged;
|
||||||
|
event Events.EventAsync OnTrackSelected;
|
||||||
|
|
||||||
// Control methods
|
// Control methods
|
||||||
Task SelectTrack(TrackEntity track);
|
Task SelectTrack(TrackEntity track);
|
||||||
Task Stop();
|
Task Stop();
|
||||||
|
Task Unload();
|
||||||
Task TogglePlayPause();
|
Task TogglePlayPause();
|
||||||
Task Seek(double position);
|
Task Seek(double position);
|
||||||
Task SetVolume(double volume);
|
Task SetVolume(double volume);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using DeepDrftModels.Entities;
|
using DeepDrftModels.Entities;
|
||||||
|
using NetBlocks.Models;
|
||||||
|
|
||||||
namespace DeepDrftWeb.Client.Services;
|
namespace DeepDrftWeb.Client.Services;
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ public class PlayerService : IPlayerService
|
|||||||
// IPlayerService state properties with defensive checks
|
// IPlayerService state properties with defensive checks
|
||||||
public bool IsInitialized => _isInitialized;
|
public bool IsInitialized => _isInitialized;
|
||||||
public bool IsLoaded => _isInitialized && _audioEngine?.IsLoaded == true;
|
public bool IsLoaded => _isInitialized && _audioEngine?.IsLoaded == true;
|
||||||
|
public bool IsLoading => _isInitialized && _audioEngine?.IsLoading == true;
|
||||||
public bool IsPlaying => _isInitialized && _audioEngine?.IsPlaying == true;
|
public bool IsPlaying => _isInitialized && _audioEngine?.IsPlaying == true;
|
||||||
public bool IsPaused => _isInitialized && _audioEngine?.IsPaused == true;
|
public bool IsPaused => _isInitialized && _audioEngine?.IsPaused == true;
|
||||||
public double CurrentTime => _isInitialized ? _audioEngine?.CurrentTime ?? 0.0 : 0.0;
|
public double CurrentTime => _isInitialized ? _audioEngine?.CurrentTime ?? 0.0 : 0.0;
|
||||||
@@ -24,6 +26,7 @@ public class PlayerService : IPlayerService
|
|||||||
public string? ErrorMessage => _isInitialized ? _audioEngine?.ErrorMessage : null;
|
public string? ErrorMessage => _isInitialized ? _audioEngine?.ErrorMessage : null;
|
||||||
|
|
||||||
public event Action? OnStateChanged;
|
public event Action? OnStateChanged;
|
||||||
|
public event Events.EventAsync? OnTrackSelected;
|
||||||
|
|
||||||
public async Task SelectTrack(TrackEntity track)
|
public async Task SelectTrack(TrackEntity track)
|
||||||
{
|
{
|
||||||
@@ -32,9 +35,15 @@ public class PlayerService : IPlayerService
|
|||||||
await EnsureInitializedAsync();
|
await EnsureInitializedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Immediately notify UI that track selection is happening
|
||||||
|
OnStateChanged?.Invoke();
|
||||||
|
|
||||||
|
if (OnTrackSelected != null) await OnTrackSelected.Invoke();
|
||||||
|
|
||||||
if (_isInitialized && _audioEngine != null)
|
if (_isInitialized && _audioEngine != null)
|
||||||
{
|
{
|
||||||
await _audioEngine.LoadTrack(track);
|
await _audioEngine.LoadTrack(track);
|
||||||
|
// Force a state change to ensure UI reflects final loaded state
|
||||||
OnStateChanged?.Invoke();
|
OnStateChanged?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +56,14 @@ public class PlayerService : IPlayerService
|
|||||||
OnStateChanged?.Invoke();
|
OnStateChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task Unload()
|
||||||
|
{
|
||||||
|
if (!_isInitialized || _audioEngine == null) return;
|
||||||
|
|
||||||
|
await _audioEngine.Unload();
|
||||||
|
OnStateChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task TogglePlayPause()
|
public async Task TogglePlayPause()
|
||||||
{
|
{
|
||||||
if (!_isInitialized || _audioEngine == null) return;
|
if (!_isInitialized || _audioEngine == null) return;
|
||||||
@@ -92,6 +109,7 @@ public class PlayerService : IPlayerService
|
|||||||
// Wire up engine events to trigger state change notifications
|
// Wire up engine events to trigger state change notifications
|
||||||
_audioEngine.OnProgressChanged += async _ => OnStateChanged?.Invoke();
|
_audioEngine.OnProgressChanged += async _ => OnStateChanged?.Invoke();
|
||||||
_audioEngine.OnPlaybackEnded += async () => OnStateChanged?.Invoke();
|
_audioEngine.OnPlaybackEnded += async () => OnStateChanged?.Invoke();
|
||||||
|
_audioEngine.OnLoadChanged += async _ => OnStateChanged?.Invoke();
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
OnStateChanged?.Invoke();
|
OnStateChanged?.Invoke();
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
|
||||||
|
namespace DeepDrftWeb.Services.Data;
|
||||||
|
|
||||||
|
public class DeepDrftContextFactory : IDesignTimeDbContextFactory<DeepDrftContext>
|
||||||
|
{
|
||||||
|
public DeepDrftContext CreateDbContext(string[] args)
|
||||||
|
{
|
||||||
|
var optionsBuilder = new DbContextOptionsBuilder<DeepDrftContext>();
|
||||||
|
optionsBuilder.UseSqlite("Data Source=../Database/deepdrft.db");
|
||||||
|
|
||||||
|
return new DeepDrftContext(optionsBuilder.Options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.8">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -33,13 +33,15 @@
|
|||||||
<Folder Include="wwwroot\js\" />
|
<Folder Include="wwwroot\js\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Prevent TypeScript compilation issues during publish -->
|
<!-- TypeScript configuration following Microsoft recommendations -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TypeScriptCompileOnSaveEnabled>false</TypeScriptCompileOnSaveEnabled>
|
<TypeScriptCompileOnSaveEnabled>false</TypeScriptCompileOnSaveEnabled>
|
||||||
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
|
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
|
||||||
|
<TypeScriptESModuleInterop>true</TypeScriptESModuleInterop>
|
||||||
|
<TypeScriptAllowSyntheticDefaultImports>true</TypeScriptAllowSyntheticDefaultImports>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Only copy tsconfig.json to root output, not nested publish folders -->
|
<!-- Prevent tsconfig.json from being copied to output directories -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="tsconfig.json">
|
<None Update="tsconfig.json">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ interface AudioState {
|
|||||||
|
|
||||||
type ProgressCallback = (currentTime: number) => void;
|
type ProgressCallback = (currentTime: number) => void;
|
||||||
type EndCallback = () => void;
|
type EndCallback = () => void;
|
||||||
type LoadProgressCallback = (progress: number) => void;
|
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
webkitAudioContext?: typeof AudioContext;
|
webkitAudioContext?: typeof AudioContext;
|
||||||
@@ -40,7 +39,6 @@ class AudioPlayer {
|
|||||||
private duration: number = 0;
|
private duration: number = 0;
|
||||||
private onProgressCallback: ProgressCallback | null = null;
|
private onProgressCallback: ProgressCallback | null = null;
|
||||||
private onEndCallback: EndCallback | null = null;
|
private onEndCallback: EndCallback | null = null;
|
||||||
private onLoadProgressCallback: LoadProgressCallback | null = null;
|
|
||||||
private progressInterval: number | null = null;
|
private progressInterval: number | null = null;
|
||||||
private bufferChunks: Uint8Array[] = [];
|
private bufferChunks: Uint8Array[] = [];
|
||||||
private expectedSize: number = 0;
|
private expectedSize: number = 0;
|
||||||
@@ -72,12 +70,6 @@ class AudioPlayer {
|
|||||||
try {
|
try {
|
||||||
this.bufferChunks.push(audioBlock);
|
this.bufferChunks.push(audioBlock);
|
||||||
this.currentSize += audioBlock.length;
|
this.currentSize += audioBlock.length;
|
||||||
|
|
||||||
if (this.expectedSize > 0 && this.onLoadProgressCallback) {
|
|
||||||
const progress = (this.currentSize / this.expectedSize) * 100;
|
|
||||||
this.onLoadProgressCallback(Math.min(progress, 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: (error as Error).message };
|
return { success: false, error: (error as Error).message };
|
||||||
@@ -101,10 +93,6 @@ class AudioPlayer {
|
|||||||
this.bufferChunks = [];
|
this.bufferChunks = [];
|
||||||
this.currentSize = 0;
|
this.currentSize = 0;
|
||||||
|
|
||||||
if (this.onLoadProgressCallback) {
|
|
||||||
this.onLoadProgressCallback(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
duration: this.duration,
|
duration: this.duration,
|
||||||
@@ -297,8 +285,19 @@ class AudioPlayer {
|
|||||||
this.onEndCallback = callback;
|
this.onEndCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnLoadProgressCallback(callback: LoadProgressCallback): void {
|
unload(): AudioResult {
|
||||||
this.onLoadProgressCallback = callback;
|
try {
|
||||||
|
this.stop();
|
||||||
|
this.audioBuffer = null;
|
||||||
|
this.duration = 0;
|
||||||
|
this.bufferChunks = [];
|
||||||
|
this.currentSize = 0;
|
||||||
|
this.expectedSize = 0;
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: (error as Error).message };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
@@ -387,6 +386,14 @@ const DeepDrftAudio = {
|
|||||||
return player.stop();
|
return player.stop();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unload: (playerId: string): AudioResult => {
|
||||||
|
const player = audioPlayers.get(playerId);
|
||||||
|
if (!player) {
|
||||||
|
return { success: false, error: "Player not found" };
|
||||||
|
}
|
||||||
|
return player.unload();
|
||||||
|
},
|
||||||
|
|
||||||
seek: (playerId: string, position: number): AudioResult => {
|
seek: (playerId: string, position: number): AudioResult => {
|
||||||
const player = audioPlayers.get(playerId);
|
const player = audioPlayers.get(playerId);
|
||||||
if (!player) {
|
if (!player) {
|
||||||
@@ -445,19 +452,6 @@ const DeepDrftAudio = {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
setOnLoadProgressCallback: (playerId: string, dotNetObjectReference: DotNetObjectReference, methodName: string): AudioResult => {
|
|
||||||
const player = audioPlayers.get(playerId);
|
|
||||||
if (!player) {
|
|
||||||
return { success: false, error: "Player not found" };
|
|
||||||
}
|
|
||||||
|
|
||||||
player.setOnLoadProgressCallback((progress: number) => {
|
|
||||||
dotNetObjectReference.invokeMethodAsync(methodName, progress);
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
},
|
|
||||||
|
|
||||||
disposePlayer: (playerId: string): AudioResult => {
|
disposePlayer: (playerId: string): AudioResult => {
|
||||||
const player = audioPlayers.get(playerId);
|
const player = audioPlayers.get(playerId);
|
||||||
if (player) {
|
if (player) {
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2016",
|
"target": "ESNext",
|
||||||
"module": "ES6", // or "ESNext"
|
"module": "ESNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"removeComments": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "wwwroot/js"
|
"outDir": "wwwroot/js"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts"
|
"Interop/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"bin/**/*",
|
"bin/**/*",
|
||||||
"obj/**/*"
|
"obj/**/*",
|
||||||
|
"publish/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
/* Surface Colors */
|
/* Surface Colors */
|
||||||
--deepdrft-surface: rgba(255, 255, 255, 1);
|
--deepdrft-surface: rgba(255, 255, 255, 1);
|
||||||
--deepdrft-surface-alpha: rgba(255, 255, 255, 0.9);
|
--deepdrft-surface-alpha: rgba(255, 255, 255, 0.9);
|
||||||
|
--deepdrft-theme-background-gray: rgba(26, 26, 26, 0.5);
|
||||||
|
|
||||||
/* Theme-aware Variables (Light Mode Default) */
|
/* Theme-aware Variables (Light Mode Default) */
|
||||||
--deepdrft-theme-primary: var(--deepdrft-primary);
|
--deepdrft-theme-primary: var(--deepdrft-primary);
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ echo "🚀 Starting CLI deployment process..."
|
|||||||
# start SSH agent and add key
|
# start SSH agent and add key
|
||||||
echo "🔑 Starting SSH agent and adding deployment key..."
|
echo "🔑 Starting SSH agent and adding deployment key..."
|
||||||
eval $(ssh-agent -s)
|
eval $(ssh-agent -s)
|
||||||
ssh-add /c/.ssh/deepdrft_ed25519
|
ssh-add /c/.ssh/deepdrft_dch6_ed25519
|
||||||
echo "✅ SSH agent configured"
|
echo "✅ SSH agent configured"
|
||||||
|
|
||||||
CLI_PROJ="DeepDrftCli"
|
CLI_PROJ="DeepDrftCli"
|
||||||
@@ -37,7 +37,7 @@ tar -czf $CLI_APP -C $CLI_PROJ/publish .
|
|||||||
echo "✅ Package created: $CLI_APP"
|
echo "✅ Package created: $CLI_APP"
|
||||||
|
|
||||||
# Deploy
|
# Deploy
|
||||||
REMOTE="deepdrft@dch5.snailbird.net"
|
REMOTE="deepdrft@dch6.snailbird.net"
|
||||||
CLI_APPROOT="/deepdrft/cli"
|
CLI_APPROOT="/deepdrft/cli"
|
||||||
|
|
||||||
echo "🌐 Deploying to remote server: $REMOTE"
|
echo "🌐 Deploying to remote server: $REMOTE"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# start SSH agent and add key
|
# start SSH agent and add key
|
||||||
eval $(ssh-agent -s)
|
eval $(ssh-agent -s)
|
||||||
ssh-add /c/.ssh/deepdrft_ed25519
|
ssh-add /c/.ssh/deepdrft_dch6_ed25519
|
||||||
|
|
||||||
CONTENT_PROJ="DeepDrftContent"
|
CONTENT_PROJ="DeepDrftContent"
|
||||||
WEB_PROJ="DeepDrftWeb"
|
WEB_PROJ="DeepDrftWeb"
|
||||||
@@ -14,18 +14,18 @@ dotnet publish $WEB_PROJ -c Release -f net9.0 -o $WEB_PROJ/publish -r linux-x64
|
|||||||
|
|
||||||
# Check if migrations are needed
|
# Check if migrations are needed
|
||||||
WEB_MIG="deepdrft-migrations.sql"
|
WEB_MIG="deepdrft-migrations.sql"
|
||||||
REMOTE="deepdrft@dch5.snailbird.net"
|
REMOTE="deepdrft@dch6.snailbird.net"
|
||||||
WEB_APPROOT="/deepdrft/web"
|
WEB_APPROOT="/deepdrft/web"
|
||||||
|
|
||||||
LATEST_MIGRATION=$(dotnet ef migrations list --project $WEB_SERVICES_PROJ --context DeepDrftContext --no-build | tail -1)
|
LATEST_MIGRATION=$(dotnet ef migrations list --project $WEB_SERVICES_PROJ --context DeepDrftContext | tail -1)
|
||||||
REMOTE_MIGRATION=$(ssh $REMOTE "sqlite3 $WEB_APPROOT/Database/deepdrft.db 'SELECT MigrationId FROM __EFMigrationsHistory ORDER BY MigrationId DESC LIMIT 1;'" 2>/dev/null || echo "")
|
REMOTE_MIGRATION=$(ssh $REMOTE "sqlite3 $WEB_APPROOT/Database/deepdrft.db 'SELECT MigrationId FROM __EFMigrationsHistory ORDER BY MigrationId DESC LIMIT 1;'" 2>/dev/null || echo "")
|
||||||
|
|
||||||
if [ "$LATEST_MIGRATION" != "$REMOTE_MIGRATION" ]; then
|
if [ "$LATEST_MIGRATION" != "$REMOTE_MIGRATION" ]; then
|
||||||
echo "Generating migration script from $REMOTE_MIGRATION to $LATEST_MIGRATION..."
|
echo "Generating migration script from $REMOTE_MIGRATION to $LATEST_MIGRATION..."
|
||||||
if [ -z "$REMOTE_MIGRATION" ]; then
|
if [ -z "$REMOTE_MIGRATION" ]; then
|
||||||
dotnet ef migrations script --project $WEB_SERVICES_PROJ --context DeepDrftContext --output $WEB_MIG --verbose --no-build
|
dotnet ef migrations script --project $WEB_SERVICES_PROJ --context DeepDrftContext --output $WEB_MIG --verbose
|
||||||
else
|
else
|
||||||
dotnet ef migrations script $REMOTE_MIGRATION --project $WEB_SERVICES_PROJ --context DeepDrftContext --output $WEB_MIG --verbose --no-build
|
dotnet ef migrations script $REMOTE_MIGRATION --project $WEB_SERVICES_PROJ --context DeepDrftContext --output $WEB_MIG --verbose
|
||||||
fi
|
fi
|
||||||
APPLY_MIGRATIONS=true
|
APPLY_MIGRATIONS=true
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user