-
-
+
+ @if (!string.IsNullOrEmpty(ErrorMessage))
+ {
+
+ @ErrorMessage
+
+ }
- @* Spacer div to maintain layout spacing *@
-
-}
+ @* Spacer to prevent content overlap *@
+
+}
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.cs b/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.cs
index 9877e40..457cec1 100644
--- a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.cs
+++ b/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.cs
@@ -1,4 +1,4 @@
-using DeepDrftWeb.Client.Services;
+using DeepDrftWeb.Client.Services;
using Microsoft.AspNetCore.Components;
using MudBlazor;
@@ -7,10 +7,11 @@ namespace DeepDrftWeb.Client.Controls.AudioPlayerBar;
public partial class AudioPlayerBar : 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;
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.css b/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.css
index a6737bf..c7da143 100644
--- a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.css
+++ b/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar.razor.css
@@ -1,26 +1,24 @@
-/* AudioPlayerBar Component - Scoped Styles */
+/* Preserve key visual styles while simplifying layout */
-/* Outer container - full width, fixed to bottom */
-.deepdrft-player-outer-container {
+/* Player outer container - fixed positioning */
+.player-outer-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
- width: 100%;
z-index: 1200;
padding: 0;
margin: 0;
}
-/* Inner container wrapper */
-.deepdrft-player-inner-container {
+/* Player inner container */
+.player-inner-container {
padding: 1rem;
padding-bottom: 1.5rem;
- margin: 0 auto;
}
-/* Player bar with rounded corners and semi-opaque background */
-.deepdrft-audio-player-bar {
+/* Custom backdrop blur container */
+.player-backdrop {
position: relative;
background: var(--deepdrft-theme-background-gray);
backdrop-filter: blur(15px);
@@ -29,21 +27,21 @@
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);
- transition: all 0.3s ease;
color: #ffffff;
+ transition: all 0.3s ease;
overflow: hidden;
margin-bottom: 1rem;
}
-/* Spacer div to maintain layout spacing */
-.deepdrft-player-spacer {
- height: 140px;
- width: 100%;
- flex-shrink: 0;
+/* Control buttons positioning */
+.player-controls {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
}
-/* Minimized floating dock */
-.deepdrft-minimized-player-dock {
+/* Minimized floating dock with gradient */
+.minimized-dock {
position: fixed;
bottom: 60px;
right: 60px;
@@ -51,148 +49,83 @@
width: 60px;
height: 60px;
border-radius: 50%;
- display: flex;
- justify-content: center;
- align-items: center;
+ 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;
- cursor: pointer;
}
-.deepdrft-minimized-player-dock:hover {
+.minimized-dock:hover {
transform: scale(1.1);
- box-shadow: 0 6px 25px rgba(var(--mud-palette-primary-rgb), 0.8),
+ box-shadow: 0 6px 25px rgba(var(--deepdrft-theme-primary), 0.8),
0 3px 15px rgba(0, 0, 0, 0.4);
}
-.deepdrft-minimized-player-button {
- border-radius: 50%;
+/* Minimized button styles */
+.minimized-button {
+ border-radius: 50% !important;
background: transparent !important;
- color: white;
- transition: all 0.3s ease;
+ color: white !important;
+ transition: all 0.3s ease !important;
box-shadow: none !important;
border: none !important;
width: 48px !important;
height: 48px !important;
}
-/* Layout containers */
-.deepdrft-audio-player-content {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- padding: 0.75rem 1.25rem;
+/* Spacer to prevent content overlap */
+.player-spacer {
+ height: 140px;
+ width: 100%;
+ flex-shrink: 0;
}
-.deepdrft-audio-controls-section {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
+/* Essential layout adjustments only */
+.controls-left {
min-width: 200px;
}
-.deepdrft-audio-buttons-row {
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.deepdrft-audio-info-row {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- margin-top: 0.25rem;
- justify-items: center;
-}
-
-.deepdrft-audio-volume-section {
- display: flex;
- align-items: center;
- gap: 0.25rem;
- width: 140px;
-}
-
-.deepdrft-audio-minimize-section {
- position: absolute;
- top: 0.5rem;
- right: 0.5rem;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-/* Control elements */
-.deepdrft-audio-seek-slider {
+.seekbar-flex {
flex: 1;
- margin-right: 0.5rem;
}
-.deepdrft-audio-volume-slider {
- width: 100px;
+.volume-right {
+ /*min-width: 140px;*/
}
-.deepdrft-audio-time {
- min-width: 120px;
-}
-
-.deepdrft-audio-volume-icon {
- margin-right: 4px;
-}
-
-/* Mobile responsiveness */
+/* Mobile responsive adjustments */
@media (max-width: 768px) {
- .deepdrft-minimized-player-dock {
+ .minimized-dock {
bottom: 15px;
right: 15px;
width: 56px;
height: 56px;
}
- .deepdrft-minimized-player-button {
+ .minimized-button {
width: 44px !important;
height: 44px !important;
}
- .deepdrft-player-inner-container {
+ .player-inner-container {
padding: 0.75rem;
padding-bottom: 1.25rem;
}
- .deepdrft-audio-player-bar {
+ .player-backdrop {
border-radius: 1rem;
margin-bottom: 1.25rem;
}
- .deepdrft-audio-player-content {
- flex-direction: column;
- gap: 0.5rem;
- padding: 0.75rem 1rem;
- }
-
- .deepdrft-audio-controls-section {
- align-items: center;
- width: 100%;
- }
-
- .deepdrft-audio-seek-slider {
- margin-right: 0;
- width: 100%;
- }
-
- .deepdrft-audio-volume-section {
- width: 100%;
- justify-content: center;
- }
-
- .deepdrft-player-spacer {
+ .player-spacer {
height: 160px;
}
}
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor b/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor
deleted file mode 100644
index e52e1f3..0000000
--- a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor
+++ /dev/null
@@ -1,103 +0,0 @@
-@if (_isMinimized)
-{
-
-
-
-}
-else
-{
-
-
-
-
- @* Desktop Layout *@
-
-
- @* Mobile Layout *@
-
-
-
-
- @if (IsLoading)
- {
-
- }
-
-
-
-
-
-
-
-
- @* Control Buttons - positioned absolutely like original *@
-
-
-
-
-
-
-
- @if (!string.IsNullOrEmpty(ErrorMessage))
- {
-
- @ErrorMessage
-
- }
-
-
- @* Spacer to prevent content overlap *@
-
-}
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor.cs b/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor.cs
deleted file mode 100644
index 02cdd55..0000000
--- a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-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;
- }
-}
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor.css b/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor.css
deleted file mode 100644
index c7da143..0000000
--- a/DeepDrftWeb.Client/Controls/AudioPlayerBar/AudioPlayerBar2.razor.css
+++ /dev/null
@@ -1,131 +0,0 @@
-/* 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;
- }
-}
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerProvider.razor b/DeepDrftWeb.Client/Controls/AudioPlayerProvider.razor
new file mode 100644
index 0000000..c7c9cd8
--- /dev/null
+++ b/DeepDrftWeb.Client/Controls/AudioPlayerProvider.razor
@@ -0,0 +1,3 @@
+
+ @ChildContent
+
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerProvider.razor.cs b/DeepDrftWeb.Client/Controls/AudioPlayerProvider.razor.cs
new file mode 100644
index 0000000..1356683
--- /dev/null
+++ b/DeepDrftWeb.Client/Controls/AudioPlayerProvider.razor.cs
@@ -0,0 +1,31 @@
+using DeepDrftWeb.Client.Services;
+using DeepDrftWeb.Client.Clients;
+using Microsoft.AspNetCore.Components;
+
+namespace DeepDrftWeb.Client.Controls;
+
+public partial class AudioPlayerProvider : ComponentBase
+{
+ [Inject] public required AudioInteropService AudioInterop { get; set; }
+ [Inject] public required TrackMediaClient TrackMediaClient { get; set; }
+
+ private AudioPlayerService? _audioPlayerService;
+
+ [Parameter] public RenderFragment? ChildContent { get; set; }
+
+ protected override void OnInitialized()
+ {
+ // Create the service immediately (but don't initialize yet)
+ _audioPlayerService = new AudioPlayerService(AudioInterop, TrackMediaClient);
+ }
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender && _audioPlayerService != null)
+ {
+ // Initialize the service after render when JavaScript is available
+ await _audioPlayerService.InitializeAsync();
+ StateHasChanged();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerService.razor b/DeepDrftWeb.Client/Controls/AudioPlayerService.razor
deleted file mode 100644
index 6292516..0000000
--- a/DeepDrftWeb.Client/Controls/AudioPlayerService.razor
+++ /dev/null
@@ -1,3 +0,0 @@
-
- @ChildContent
-
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Controls/AudioPlayerService.razor.cs b/DeepDrftWeb.Client/Controls/AudioPlayerService.razor.cs
deleted file mode 100644
index 66c7bdf..0000000
--- a/DeepDrftWeb.Client/Controls/AudioPlayerService.razor.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using DeepDrftWeb.Client.Services;
-using Microsoft.AspNetCore.Components;
-using DeepDrftModels.Entities;
-
-namespace DeepDrftWeb.Client.Controls;
-
-public partial class AudioPlayerService : ComponentBase
-{
- [Inject] public required AudioPlaybackEngine AudioPlaybackEngine { get; set; }
-
- private readonly PlayerService _playerService = new();
- private IPlayerService PlayerService => _playerService;
-
- [Parameter] public RenderFragment? ChildContent { get; set; }
-
- protected override async Task OnAfterRenderAsync(bool firstRender)
- {
- if (firstRender)
- {
- // Initialize the PlayerService with the AudioPlaybackEngine now that it's available
- await _playerService.InitializeAsync(AudioPlaybackEngine);
- StateHasChanged();
- }
- }
-}
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Layout/MainLayout.razor b/DeepDrftWeb.Client/Layout/MainLayout.razor
index 1df49e9..f6cc905 100644
--- a/DeepDrftWeb.Client/Layout/MainLayout.razor
+++ b/DeepDrftWeb.Client/Layout/MainLayout.razor
@@ -7,7 +7,7 @@
-
+
@@ -20,9 +20,9 @@
@Body
-
+
-
+
diff --git a/DeepDrftWeb.Client/Services/AudioPlaybackEngine.cs b/DeepDrftWeb.Client/Services/AudioPlaybackEngine.cs
index 3469a38..ee420be 100644
--- a/DeepDrftWeb.Client/Services/AudioPlaybackEngine.cs
+++ b/DeepDrftWeb.Client/Services/AudioPlaybackEngine.cs
@@ -1,4 +1,7 @@
-using DeepDrftModels.Entities;
+// DEPRECATED: This class has been merged into AudioPlayerService
+// TODO: Remove after testing new implementation
+/*
+using DeepDrftModels.Entities;
using DeepDrftWeb.Client.Clients;
using NetBlocks.Models;
@@ -357,4 +360,5 @@ public class AudioPlaybackEngine : IAsyncDisposable
{
await AudioInterop.DisposePlayerAsync(PlayerId);
}
-}
\ No newline at end of file
+}
+*/
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Services/AudioPlayerService.cs b/DeepDrftWeb.Client/Services/AudioPlayerService.cs
new file mode 100644
index 0000000..b023972
--- /dev/null
+++ b/DeepDrftWeb.Client/Services/AudioPlayerService.cs
@@ -0,0 +1,400 @@
+using DeepDrftModels.Entities;
+using DeepDrftWeb.Client.Clients;
+using NetBlocks.Models;
+
+namespace DeepDrftWeb.Client.Services;
+
+public class AudioPlayerService : IPlayerService, IAsyncDisposable
+{
+ private readonly AudioInteropService _audioInterop;
+ private readonly TrackMediaClient _trackMediaClient;
+
+ public string PlayerId { get; private set; } = Guid.NewGuid().ToString();
+
+ // State properties
+ public bool IsInitialized { 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 IsPaused { get; private set; } = false;
+ public double CurrentTime { get; private set; } = 0;
+ public double? Duration { get; private set; } = null;
+ public double Volume { get; private set; } = 0.8;
+ public double LoadProgress { get; private set; } = 0;
+ public string? ErrorMessage { get; private set; }
+
+ // Events
+ public event Action? OnStateChanged;
+ public event Events.EventAsync? OnTrackSelected;
+
+ public AudioPlayerService(AudioInteropService audioInterop, TrackMediaClient trackMediaClient)
+ {
+ _audioInterop = audioInterop;
+ _trackMediaClient = trackMediaClient;
+ }
+
+ public async Task InitializeAsync()
+ {
+ if (IsInitialized) return;
+
+ try
+ {
+ var result = await _audioInterop.CreatePlayerAsync(PlayerId);
+ if (!result.Success)
+ {
+ ErrorMessage = $"Failed to initialize audio player: {result.Error}";
+ NotifyStateChanged();
+ return;
+ }
+
+ await _audioInterop.SetOnProgressCallbackAsync(PlayerId, OnProgressCallback);
+ await _audioInterop.SetOnEndCallbackAsync(PlayerId, OnPlaybackEndCallback);
+ await _audioInterop.SetOnLoadProgressCallbackAsync(PlayerId, OnLoadProgressCallback);
+
+ await _audioInterop.SetVolumeAsync(PlayerId, Volume);
+
+ IsInitialized = true;
+ ErrorMessage = null;
+ NotifyStateChanged();
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Failed to initialize audio player: {ex.Message}";
+ NotifyStateChanged();
+ }
+ }
+
+ public async Task SelectTrack(TrackEntity track)
+ {
+ await EnsureInitializedAsync();
+
+ NotifyStateChanged();
+
+ if (OnTrackSelected != null)
+ await OnTrackSelected.Invoke();
+
+ await LoadTrack(track);
+ NotifyStateChanged();
+ }
+
+ private async Task LoadTrack(TrackEntity track)
+ {
+ try
+ {
+ if (IsLoading) return;
+
+ if (IsPlaying || IsPaused)
+ {
+ await Unload();
+ }
+
+ // Reset state to indicate loading has started
+ ErrorMessage = null;
+ LoadProgress = 0;
+ IsLoaded = false;
+ IsLoading = true;
+ Duration = null;
+ CurrentTime = 0;
+ NotifyStateChanged();
+
+ var loadResult = await _audioInterop.InitializeBufferedPlayerAsync(PlayerId);
+ if (loadResult?.Success != true)
+ {
+ ErrorMessage = $"Failed to initialize audio buffer: {loadResult?.Error ?? "Unknown error"}";
+ return;
+ }
+
+ var mediaResult = await _trackMediaClient.GetTrackMedia(track.EntryKey);
+ if (!mediaResult.Success)
+ {
+ ErrorMessage = mediaResult.GetMessage();
+ return;
+ }
+
+ if (mediaResult.Value == null)
+ {
+ ErrorMessage = "No audio returned from server";
+ return;
+ }
+
+ TrackMediaResponse audio = mediaResult.Value;
+ await StreamAudio(audio);
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Error loading audio: {ex.Message}";
+ LoadProgress = 0;
+ IsLoaded = false;
+ }
+ finally
+ {
+ IsLoading = false;
+ NotifyStateChanged();
+ }
+ }
+
+ private async Task StreamAudio(TrackMediaResponse audio)
+ {
+ try
+ {
+ const int bufferSize = 32 * 1024;
+ long totalBytesRead = 0;
+ int currentBytes;
+
+ do
+ {
+ var buffer = new byte[bufferSize];
+ currentBytes = await audio.Stream.ReadAsync(buffer, 0, buffer.Length);
+
+ if (currentBytes > 0)
+ {
+ totalBytesRead += currentBytes;
+
+ if (currentBytes < bufferSize)
+ {
+ var trimmedBuffer = new byte[currentBytes];
+ Array.Copy(buffer, trimmedBuffer, currentBytes);
+ buffer = trimmedBuffer;
+ }
+
+ var appendResult = await _audioInterop.AppendAudioBlockAsync(PlayerId, buffer);
+ if (!appendResult.Success)
+ {
+ throw new Exception($"Failed to append audio block: {appendResult.Error}");
+ }
+
+ if (audio.ContentLength > 0)
+ {
+ LoadProgress = Math.Min(1.0, (double)totalBytesRead / audio.ContentLength);
+ NotifyStateChanged();
+ }
+ }
+ } while (currentBytes > 0);
+
+ var finalizeResult = await _audioInterop.FinalizeAudioBufferAsync(PlayerId);
+ if (!finalizeResult.Success)
+ {
+ throw new Exception($"Failed to finalize audio buffer: {finalizeResult.Error}");
+ }
+
+ Duration = finalizeResult.Duration;
+ LoadProgress = 1.0;
+ IsLoaded = true;
+ ErrorMessage = null;
+ NotifyStateChanged();
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Error streaming audio: {ex.Message}";
+ LoadProgress = 0;
+ IsLoaded = false;
+ NotifyStateChanged();
+ throw;
+ }
+ }
+
+ public async Task TogglePlayPause()
+ {
+ if (!IsLoaded) return;
+
+ try
+ {
+ AudioOperationResult result;
+
+ if (IsPlaying)
+ {
+ result = await _audioInterop.PauseAsync(PlayerId);
+ if (result.Success)
+ {
+ IsPlaying = false;
+ IsPaused = true;
+ }
+ }
+ else
+ {
+ result = await _audioInterop.PlayAsync(PlayerId);
+ if (result.Success)
+ {
+ IsPlaying = true;
+ IsPaused = false;
+ }
+ }
+
+ if (!result.Success)
+ {
+ ErrorMessage = $"Playback error: {result.Error}";
+ }
+ else
+ {
+ ErrorMessage = null;
+ }
+
+ NotifyStateChanged();
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Error controlling playback: {ex.Message}";
+ NotifyStateChanged();
+ }
+ }
+
+ public async Task Stop()
+ {
+ if (!IsLoaded) return;
+
+ try
+ {
+ var result = await _audioInterop.StopAsync(PlayerId);
+ if (result.Success)
+ {
+ IsPlaying = false;
+ IsPaused = false;
+ CurrentTime = 0;
+ ErrorMessage = null;
+ }
+ else
+ {
+ ErrorMessage = $"Stop error: {result.Error}";
+ }
+
+ NotifyStateChanged();
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Error stopping playback: {ex.Message}";
+ NotifyStateChanged();
+ }
+ }
+
+ 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}";
+ }
+
+ NotifyStateChanged();
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Error unloading track: {ex.Message}";
+ NotifyStateChanged();
+ }
+ }
+
+ public async Task Seek(double position)
+ {
+ if (!IsLoaded) return;
+
+ try
+ {
+ var result = await _audioInterop.SeekAsync(PlayerId, position);
+ if (result.Success)
+ {
+ CurrentTime = position;
+ ErrorMessage = null;
+ }
+ else
+ {
+ ErrorMessage = $"Seek error: {result.Error}";
+ }
+
+ NotifyStateChanged();
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Error seeking: {ex.Message}";
+ NotifyStateChanged();
+ }
+ }
+
+ public async Task SetVolume(double volume)
+ {
+ Volume = volume;
+
+ if (IsLoaded)
+ {
+ try
+ {
+ var result = await _audioInterop.SetVolumeAsync(PlayerId, volume);
+ if (!result.Success)
+ {
+ ErrorMessage = $"Volume error: {result.Error}";
+ }
+ else
+ {
+ ErrorMessage = null;
+ }
+ }
+ catch (Exception ex)
+ {
+ ErrorMessage = $"Error setting volume: {ex.Message}";
+ }
+ }
+
+ NotifyStateChanged();
+ }
+
+ public void ClearError()
+ {
+ ErrorMessage = null;
+ NotifyStateChanged();
+ }
+
+ private async Task OnProgressCallback(double currentTime)
+ {
+ CurrentTime = currentTime;
+ NotifyStateChanged();
+ }
+
+ private async Task OnPlaybackEndCallback()
+ {
+ IsPlaying = false;
+ IsPaused = false;
+ CurrentTime = 0;
+ NotifyStateChanged();
+ }
+
+ private async Task OnLoadProgressCallback(double progress)
+ {
+ LoadProgress = progress;
+ NotifyStateChanged();
+ }
+
+ private async Task EnsureInitializedAsync()
+ {
+ if (!IsInitialized)
+ {
+ await InitializeAsync();
+ }
+ }
+
+ private void NotifyStateChanged()
+ {
+ OnStateChanged?.Invoke();
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (IsInitialized)
+ {
+ await _audioInterop.DisposePlayerAsync(PlayerId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Services/IPlayerService.cs b/DeepDrftWeb.Client/Services/IPlayerService.cs
index 651827e..56d183c 100644
--- a/DeepDrftWeb.Client/Services/IPlayerService.cs
+++ b/DeepDrftWeb.Client/Services/IPlayerService.cs
@@ -22,6 +22,7 @@ public interface IPlayerService
event Events.EventAsync OnTrackSelected;
// Control methods
+ Task InitializeAsync();
Task SelectTrack(TrackEntity track);
Task Stop();
Task Unload();
diff --git a/DeepDrftWeb.Client/Services/PlayerService.cs b/DeepDrftWeb.Client/Services/PlayerService.cs
index 2cec8e8..e045662 100644
--- a/DeepDrftWeb.Client/Services/PlayerService.cs
+++ b/DeepDrftWeb.Client/Services/PlayerService.cs
@@ -1,3 +1,6 @@
+// DEPRECATED: This class has been replaced by AudioPlayerService
+// TODO: Remove after testing new implementation
+/*
using DeepDrftModels.Entities;
using NetBlocks.Models;
@@ -128,4 +131,5 @@ public class PlayerService : IPlayerService
await InitializeAsync(_audioEngine);
}
}
-}
\ No newline at end of file
+}
+*/
\ No newline at end of file
diff --git a/DeepDrftWeb.Client/Startup.cs b/DeepDrftWeb.Client/Startup.cs
index d3b8971..919c212 100644
--- a/DeepDrftWeb.Client/Startup.cs
+++ b/DeepDrftWeb.Client/Startup.cs
@@ -29,6 +29,6 @@ public static class Startup
});
services.AddScoped
();
services.AddScoped();
- services.AddTransient();
+ // AudioPlaybackEngine removed - functionality merged into AudioPlayerService
}
}
\ No newline at end of file