|
357 | 357 | gap: 0.25rem; |
358 | 358 | } |
359 | 359 |
|
360 | | - .audio-progress-bar { |
| 360 | + .audio-seek-slider { |
361 | 361 | width: 100%; |
362 | 362 | height: 6px; |
| 363 | + -webkit-appearance: none; |
| 364 | + appearance: none; |
363 | 365 | background: rgba(255, 255, 255, 0.1); |
364 | 366 | border-radius: 3px; |
365 | 367 | cursor: pointer; |
366 | | - position: relative; |
367 | | - } |
368 | | -
|
369 | | - .audio-progress-buffer { |
370 | | - position: absolute; |
371 | | - top: 0; |
372 | | - left: 0; |
373 | | - height: 100%; |
374 | | - background: rgba(255, 255, 255, 0.2); |
375 | | - border-radius: 3px; |
376 | | - transition: width 0.3s ease; |
| 368 | + outline: none; |
377 | 369 | } |
378 | 370 |
|
379 | | - .audio-progress-fill { |
380 | | - position: absolute; |
381 | | - top: 0; |
382 | | - left: 0; |
383 | | - height: 100%; |
384 | | - background: linear-gradient(90deg, #e94560 0%, #ff6b6b 100%); |
| 371 | + .audio-seek-slider::-webkit-slider-runnable-track { |
| 372 | + height: 6px; |
| 373 | + background: transparent; |
385 | 374 | border-radius: 3px; |
386 | | - transition: width 0.1s linear; |
387 | 375 | } |
388 | 376 |
|
389 | | - .audio-progress-thumb { |
390 | | - position: absolute; |
391 | | - top: 50%; |
392 | | - transform: translate(-50%, -50%); |
| 377 | + .audio-seek-slider::-webkit-slider-thumb { |
| 378 | + -webkit-appearance: none; |
| 379 | + appearance: none; |
393 | 380 | width: 14px; |
394 | 381 | height: 14px; |
395 | 382 | background: #fff; |
396 | 383 | border-radius: 50%; |
397 | 384 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); |
| 385 | + cursor: pointer; |
| 386 | + margin-top: -4px; |
398 | 387 | transition: transform 0.1s ease, box-shadow 0.2s ease; |
399 | | - z-index: 2; |
400 | 388 | } |
401 | 389 |
|
402 | | - .audio-progress-bar:hover .audio-progress-thumb { |
403 | | - transform: translate(-50%, -50%) scale(1.2); |
| 390 | + .audio-seek-slider::-webkit-slider-thumb:hover { |
| 391 | + transform: scale(1.2); |
404 | 392 | box-shadow: 0 2px 10px rgba(233, 69, 96, 0.5); |
405 | 393 | } |
406 | 394 |
|
407 | | - .audio-progress-thumb::after { |
408 | | - content: ''; |
409 | | - position: absolute; |
410 | | - top: 50%; |
411 | | - left: 50%; |
412 | | - transform: translate(-50%, -50%); |
413 | | - width: 6px; |
| 395 | + .audio-seek-slider::-moz-range-track { |
414 | 396 | height: 6px; |
415 | | - background: #e94560; |
| 397 | + background: transparent; |
| 398 | + border-radius: 3px; |
| 399 | + } |
| 400 | +
|
| 401 | + .audio-seek-slider::-moz-range-thumb { |
| 402 | + width: 14px; |
| 403 | + height: 14px; |
| 404 | + background: #fff; |
416 | 405 | border-radius: 50%; |
| 406 | + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); |
| 407 | + cursor: pointer; |
| 408 | + border: none; |
| 409 | + } |
| 410 | +
|
| 411 | + .audio-seek-slider::-moz-range-thumb:hover { |
| 412 | + transform: scale(1.2); |
| 413 | + box-shadow: 0 2px 10px rgba(233, 69, 96, 0.5); |
417 | 414 | } |
418 | 415 |
|
419 | 416 | .audio-time { |
|
765 | 762 | </div> |
766 | 763 | <div class="audio-controls-row"> |
767 | 764 | <div class="audio-progress-container"> |
768 | | - <div class="audio-progress-bar" @onclick="SeekAudio"> |
769 | | - <div class="audio-progress-buffer" style="width: @(bufferPercent.ToString("F1", System.Globalization.CultureInfo.InvariantCulture))%"></div> |
770 | | - <div class="audio-progress-fill" style="width: @(progressPercent.ToString("F1", System.Globalization.CultureInfo.InvariantCulture))%"></div> |
771 | | - <div class="audio-progress-thumb" style="left: @(progressPercent.ToString("F1", System.Globalization.CultureInfo.InvariantCulture))%"></div> |
772 | | - </div> |
| 765 | + <input type="range" class="audio-seek-slider" min="0" max="100" step="0.1" |
| 766 | + value="@progressPercent.ToString("F1", System.Globalization.CultureInfo.InvariantCulture)" |
| 767 | + style="background: linear-gradient(to right, #e94560 0%, #e94560 @(progressPercent.ToString("F1", System.Globalization.CultureInfo.InvariantCulture))%, rgba(255,255,255,0.1) @(progressPercent.ToString("F1", System.Globalization.CultureInfo.InvariantCulture))%, rgba(255,255,255,0.1) 100%);" |
| 768 | + @oninput="SeekAudioInput" |
| 769 | + @onchange="SeekAudioChange" /> |
773 | 770 | <div class="audio-time"> |
774 | 771 | <span>@currentTimeDisplay</span> |
775 | 772 | <span>@durationDisplay</span> |
|
780 | 777 | <i class="bi @(isMuted ? "bi-volume-mute-fill" : "bi-volume-up-fill")"></i> |
781 | 778 | </button> |
782 | 779 | <input type="range" class="audio-volume-slider" min="0" max="100" value="@volume" |
| 780 | + style="background: linear-gradient(to right, #e94560 0%, #e94560 @(volume)%, rgba(255,255,255,0.1) @(volume)%, rgba(255,255,255,0.1) 100%);" |
| 781 | + @oninput="ChangeVolumeInput" |
783 | 782 | @onchange="ChangeVolume" /> |
784 | 783 | </div> |
785 | 784 | <button class="audio-btn-visualizer @(enableVisualizer ? "active" : "")" @onclick="ToggleVisualizer" title="@(enableVisualizer ? "Disable visualizer" : "Enable visualizer")"> |
|
1019 | 1018 | } |
1020 | 1019 | } |
1021 | 1020 |
|
1022 | | - private async Task SeekAudio(MouseEventArgs e) |
| 1021 | + private async Task SeekAudioInput(Microsoft.AspNetCore.Components.ChangeEventArgs e) |
| 1022 | + { |
| 1023 | + // Preview while dragging (updates display but doesn't commit) |
| 1024 | + if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double percent)) |
| 1025 | + { |
| 1026 | + progressPercent = percent; |
| 1027 | + await InvokeAsync(StateHasChanged); |
| 1028 | + } |
| 1029 | + } |
| 1030 | + |
| 1031 | + private async Task SeekAudioChange(Microsoft.AspNetCore.Components.ChangeEventArgs e) |
1023 | 1032 | { |
1024 | | - var percent = e.OffsetX / 200.0 * 100; |
1025 | | - if (percent < 0) percent = 0; |
1026 | | - if (percent > 100) percent = 100; |
1027 | | - await JSRuntime.InvokeVoidAsync("seekAudioPlayer", percent); |
1028 | | - await UpdateProgress(); |
| 1033 | + // Commit the seek when user releases |
| 1034 | + if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double percent)) |
| 1035 | + { |
| 1036 | + if (percent < 0) percent = 0; |
| 1037 | + if (percent > 100) percent = 100; |
| 1038 | + await JSRuntime.InvokeVoidAsync("seekAudioPlayer", percent); |
| 1039 | + await UpdateProgress(); |
| 1040 | + } |
| 1041 | + } |
| 1042 | + |
| 1043 | + private async Task ChangeVolumeInput(Microsoft.AspNetCore.Components.ChangeEventArgs e) |
| 1044 | + { |
| 1045 | + // Update volume in real-time while dragging |
| 1046 | + if (int.TryParse(e.Value?.ToString(), out int newVolume)) |
| 1047 | + { |
| 1048 | + volume = newVolume; |
| 1049 | + isMuted = volume == 0; |
| 1050 | + await JSRuntime.InvokeVoidAsync("setAudioVolume", volume / 100.0); |
| 1051 | + await InvokeAsync(StateHasChanged); |
| 1052 | + } |
1029 | 1053 | } |
1030 | 1054 |
|
1031 | 1055 | private async Task ChangeVolume(Microsoft.AspNetCore.Components.ChangeEventArgs e) |
|
0 commit comments