using Microsoft.JSInterop; namespace DeepDrftWeb.Client.Services; public class AudioInteropService : IAsyncDisposable { private readonly IJSRuntime _jsRuntime; private readonly Dictionary> _callbacks = new(); public AudioInteropService(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } public async Task CreatePlayerAsync(string playerId) { try { var result = await _jsRuntime.InvokeAsync("DeepDrftAudio.createPlayer", playerId); return result; } catch (Exception ex) { return new AudioOperationResult { Success = false, Error = ex.Message }; } } public async Task InitializeBufferedPlayerAsync(string playerId) { return await InvokeJsAsync("DeepDrftAudio.initializeBufferedPlayer", playerId); } public async Task AppendAudioBlockAsync(string playerId, byte[] audioBlock) { return await InvokeJsAsync("DeepDrftAudio.appendAudioBlock", playerId, audioBlock); } public async Task FinalizeAudioBufferAsync(string playerId) { return await InvokeJsAsync("DeepDrftAudio.finalizeAudioBuffer", playerId); } public async Task PlayAsync(string playerId) { return await InvokeJsAsync("DeepDrftAudio.play", playerId); } public async Task PauseAsync(string playerId) { return await InvokeJsAsync("DeepDrftAudio.pause", playerId); } public async Task StopAsync(string playerId) { return await InvokeJsAsync("DeepDrftAudio.stop", playerId); } public async Task SeekAsync(string playerId, double position) { return await InvokeJsAsync("DeepDrftAudio.seek", playerId, position); } public async Task SetVolumeAsync(string playerId, double volume) { return await InvokeJsAsync("DeepDrftAudio.setVolume", playerId, volume); } public async Task GetCurrentTimeAsync(string playerId) { try { return await _jsRuntime.InvokeAsync("DeepDrftAudio.getCurrentTime", playerId); } catch (Exception) { return 0; } } public async Task GetStateAsync(string playerId) { try { return await _jsRuntime.InvokeAsync("DeepDrftAudio.getState", playerId); } catch (Exception) { return null; } } public async Task SetOnProgressCallbackAsync(string playerId, Func callback) { return await SetCallbackAsync(playerId, "_progress", "setOnProgressCallback", "OnProgressCallback", wrapper => wrapper.OnProgress = callback); } public async Task SetOnEndCallbackAsync(string playerId, Func callback) { return await SetCallbackAsync(playerId, "_end", "setOnEndCallback", "OnEndCallback", wrapper => wrapper.OnEnd = callback); } public async Task SetOnLoadProgressCallbackAsync(string playerId, Func callback) { return await SetCallbackAsync(playerId, "_loadprogress", "setOnLoadProgressCallback", "OnLoadProgressCallback", wrapper => wrapper.OnLoadProgress = callback); } public async Task DisposePlayerAsync(string playerId) { CleanupPlayerCallbacks(playerId); return await InvokeJsAsync("DeepDrftAudio.disposePlayer", playerId); } private async Task InvokeJsAsync(string identifier, params object[] args) { try { return await _jsRuntime.InvokeAsync(identifier, args); } catch (Exception ex) { if (typeof(T) == typeof(AudioOperationResult)) return (T)(object)new AudioOperationResult { Success = false, Error = ex.Message }; if (typeof(T) == typeof(AudioLoadResult)) return (T)(object)new AudioLoadResult { Success = false, Error = ex.Message }; throw; } } private async Task SetCallbackAsync(string playerId, string suffix, string jsMethod, string callbackMethod, Action configureCallback) { try { var callbackWrapper = new AudioPlayerCallback(); configureCallback(callbackWrapper); var dotNetObjectRef = DotNetObjectReference.Create(callbackWrapper); _callbacks[playerId + suffix] = dotNetObjectRef; return await _jsRuntime.InvokeAsync($"DeepDrftAudio.{jsMethod}", playerId, dotNetObjectRef, callbackMethod); } catch (Exception ex) { return new AudioOperationResult { Success = false, Error = ex.Message }; } } private void CleanupPlayerCallbacks(string playerId) { var keysToRemove = _callbacks.Keys.Where(k => k.StartsWith(playerId + "_")).ToList(); foreach (var key in keysToRemove) { _callbacks[key]?.Dispose(); _callbacks.Remove(key); } } public async ValueTask DisposeAsync() { foreach (var callback in _callbacks.Values) { callback?.Dispose(); } _callbacks.Clear(); } } public class AudioPlayerCallback { public Func? OnProgress { get; set; } public Func? OnEnd { get; set; } public Func? OnLoadProgress { get; set; } [JSInvokable] public async Task OnProgressCallback(double currentTime) { if (OnProgress != null) await OnProgress(currentTime); } [JSInvokable] public async Task OnEndCallback() { if (OnEnd != null) await OnEnd(); } [JSInvokable] public async Task OnLoadProgressCallback(double progress) { if (OnLoadProgress != null) await OnLoadProgress(progress); } } public class AudioOperationResult { public bool Success { get; set; } public string? Error { get; set; } } public class AudioLoadResult : AudioOperationResult { public double Duration { get; set; } public int SampleRate { get; set; } public int NumberOfChannels { get; set; } public double LoadProgress { get; set; } } public class AudioPlayerState { public bool IsPlaying { get; set; } public bool IsPaused { get; set; } public double CurrentTime { get; set; } public double Duration { get; set; } public double Volume { get; set; } public double LoadProgress { get; set; } }