changes
This commit is contained in:
@@ -29,7 +29,7 @@
|
|||||||
<div class="vfd-time-badge">
|
<div class="vfd-time-badge">
|
||||||
@if (RadioState.Duration > 0)
|
@if (RadioState.Duration > 0)
|
||||||
{
|
{
|
||||||
<span>@FormatTime(RadioState.CurrentTime) / @FormatTime(RadioState.Duration)</span>
|
<span>@FormatTime(DisplayTime) / @FormatTime(RadioState.Duration)</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -38,6 +38,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Interactive Track Progress Indicator / Scrubber -->
|
||||||
|
<div class="vfd-progress-wrapper @(RadioState.IsPoweredOn && RadioState.IsTuned && RadioState.Duration > 0 ? "" : "progress-disabled")">
|
||||||
|
<div class="vfd-progress-track">
|
||||||
|
<div class="vfd-progress-fill" style="width: @ProgressPercentage.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture)%"></div>
|
||||||
|
<div class="vfd-progress-thumb" style="left: @ProgressPercentage.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture)%"></div>
|
||||||
|
</div>
|
||||||
|
<input type="range" class="vfd-progress-slider"
|
||||||
|
min="0"
|
||||||
|
max="@(RadioState.Duration > 0 ? RadioState.Duration : 100)"
|
||||||
|
step="0.2"
|
||||||
|
value="@DisplayTime"
|
||||||
|
@oninput="HandleProgressInput"
|
||||||
|
@onchange="HandleProgressChange"
|
||||||
|
disabled="@(!RadioState.IsPoweredOn || !RadioState.IsTuned || RadioState.Duration <= 0)"
|
||||||
|
title="Drag or click to seek" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="vfd-track-meta">
|
<div class="vfd-track-meta">
|
||||||
@if (RadioState.IsPoweredOn && RadioState.CurrentTrack != null && RadioState.IsTuned)
|
@if (RadioState.IsPoweredOn && RadioState.CurrentTrack != null && RadioState.IsTuned)
|
||||||
{
|
{
|
||||||
@@ -81,7 +98,12 @@
|
|||||||
<div class="vfd-lyrics-panel">
|
<div class="vfd-lyrics-panel">
|
||||||
<div class="vfd-panel-header">
|
<div class="vfd-panel-header">
|
||||||
<span>LYRICS & PROMPT</span>
|
<span>LYRICS & PROMPT</span>
|
||||||
<span class="vfd-autoscroll-badge">AUTO-SCROLL</span>
|
<button type="button" class="vfd-autoscroll-toggle @(RadioState.IsAutoScrollEnabled ? "autoscroll-on" : "autoscroll-off")"
|
||||||
|
@onclick="RadioState.ToggleAutoScrollAsync"
|
||||||
|
title="Click to toggle auto-scrolling lyrics ON / OFF">
|
||||||
|
<span class="autoscroll-led">●</span>
|
||||||
|
AUTO-SCROLL @(RadioState.IsAutoScrollEnabled ? "ON" : "OFF")
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="vfd-lyrics-container">
|
<div class="vfd-lyrics-container">
|
||||||
@@ -107,6 +129,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _isDragging = false;
|
||||||
|
private double _dragTime = 0;
|
||||||
|
|
||||||
|
private double DisplayTime => _isDragging ? _dragTime : RadioState.CurrentTime;
|
||||||
|
|
||||||
|
private double ProgressPercentage => RadioState.Duration > 0
|
||||||
|
? Math.Clamp((DisplayTime / RadioState.Duration) * 100.0, 0.0, 100.0)
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
RadioState.OnStateChanged += HandleStateChanged;
|
RadioState.OnStateChanged += HandleStateChanged;
|
||||||
@@ -114,12 +145,36 @@
|
|||||||
|
|
||||||
private void HandleStateChanged()
|
private void HandleStateChanged()
|
||||||
{
|
{
|
||||||
InvokeAsync(StateHasChanged);
|
if (!_isDragging)
|
||||||
|
{
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleProgressInput(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double val))
|
||||||
|
{
|
||||||
|
_isDragging = true;
|
||||||
|
_dragTime = val;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleProgressChange(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
if (double.TryParse(e.Value?.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double val))
|
||||||
|
{
|
||||||
|
_dragTime = val;
|
||||||
|
await RadioState.SeekAsync(_dragTime);
|
||||||
|
}
|
||||||
|
_isDragging = false;
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatTime(double seconds)
|
private string FormatTime(double seconds)
|
||||||
{
|
{
|
||||||
var ts = TimeSpan.FromSeconds(seconds);
|
var ts = TimeSpan.FromSeconds(Math.Max(0, seconds));
|
||||||
return $"{(int)ts.TotalMinutes:D2}:{ts.Seconds:D2}";
|
return $"{(int)ts.TotalMinutes:D2}:{ts.Seconds:D2}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ public class RadioLibraryService
|
|||||||
foreach (System.Text.RegularExpressions.Match m in matches)
|
foreach (System.Text.RegularExpressions.Match m in matches)
|
||||||
{
|
{
|
||||||
var href = m.Groups[1].Value.Trim();
|
var href = m.Groups[1].Value.Trim();
|
||||||
href = href.TrimEnd('/');
|
href = Uri.UnescapeDataString(href).TrimEnd('/');
|
||||||
var name = Path.GetFileName(href);
|
var name = Path.GetFileName(href);
|
||||||
if (string.IsNullOrWhiteSpace(name) || name.StartsWith(".") || name.Equals("audio", StringComparison.OrdinalIgnoreCase))
|
if (string.IsNullOrWhiteSpace(name) || name.StartsWith(".") || name.Equals("audio", StringComparison.OrdinalIgnoreCase))
|
||||||
continue;
|
continue;
|
||||||
@@ -144,6 +144,7 @@ public class RadioLibraryService
|
|||||||
foreach (System.Text.RegularExpressions.Match m in matches)
|
foreach (System.Text.RegularExpressions.Match m in matches)
|
||||||
{
|
{
|
||||||
var href = m.Groups[1].Value.Trim();
|
var href = m.Groups[1].Value.Trim();
|
||||||
|
href = Uri.UnescapeDataString(href);
|
||||||
var name = Path.GetFileName(href);
|
var name = Path.GetFileName(href);
|
||||||
var ext = Path.GetExtension(name);
|
var ext = Path.GetExtension(name);
|
||||||
if (validExts.Contains(ext))
|
if (validExts.Contains(ext))
|
||||||
@@ -243,14 +244,15 @@ public class RadioLibraryService
|
|||||||
|
|
||||||
private RadioTrack BuildTrack(string folder, string fileName, string? defaultCover)
|
private RadioTrack BuildTrack(string folder, string fileName, string? defaultCover)
|
||||||
{
|
{
|
||||||
var nameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
|
var unescapedFileName = Uri.UnescapeDataString(fileName);
|
||||||
|
var nameWithoutExt = Path.GetFileNameWithoutExtension(unescapedFileName);
|
||||||
ParseArtistAndTitle(nameWithoutExt, out string artist, out string title);
|
ParseArtistAndTitle(nameWithoutExt, out string artist, out string title);
|
||||||
|
|
||||||
var track = new RadioTrack
|
var track = new RadioTrack
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Artist = artist,
|
Artist = artist,
|
||||||
FileName = fileName,
|
FileName = unescapedFileName,
|
||||||
AudioUrl = $"audio/{folder}/{fileName}",
|
AudioUrl = $"audio/{folder}/{fileName}",
|
||||||
LyricsUrl = $"audio/{folder}/{nameWithoutExt}.txt",
|
LyricsUrl = $"audio/{folder}/{nameWithoutExt}.txt",
|
||||||
CoverUrl = defaultCover
|
CoverUrl = defaultCover
|
||||||
@@ -261,10 +263,11 @@ public class RadioLibraryService
|
|||||||
|
|
||||||
private void ParseArtistAndTitle(string nameWithoutExt, out string artist, out string title)
|
private void ParseArtistAndTitle(string nameWithoutExt, out string artist, out string title)
|
||||||
{
|
{
|
||||||
|
nameWithoutExt = Uri.UnescapeDataString(nameWithoutExt);
|
||||||
if (nameWithoutExt.Contains(" - "))
|
if (nameWithoutExt.Contains(" - "))
|
||||||
{
|
{
|
||||||
var parts = nameWithoutExt.Split(new[] { " - " }, 2, StringSplitOptions.TrimEntries);
|
var parts = nameWithoutExt.Split(new[] { " - " }, 2, StringSplitOptions.TrimEntries);
|
||||||
artist = parts[0];
|
artist = FormatName(parts[0]);
|
||||||
title = FormatName(parts[1]);
|
title = FormatName(parts[1]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -276,14 +279,17 @@ public class RadioLibraryService
|
|||||||
|
|
||||||
private string FormatName(string raw)
|
private string FormatName(string raw)
|
||||||
{
|
{
|
||||||
var cleaned = raw.Replace('_', ' ').Replace('-', ' ').Trim();
|
var unescaped = Uri.UnescapeDataString(raw);
|
||||||
|
var cleaned = unescaped.Replace('_', ' ').Trim();
|
||||||
|
cleaned = System.Text.RegularExpressions.Regex.Replace(cleaned, @"\s+", " ");
|
||||||
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(cleaned);
|
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(cleaned);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatStationName(string folderName)
|
private string FormatStationName(string folderName)
|
||||||
{
|
{
|
||||||
// Replace underscore with space
|
var unescaped = Uri.UnescapeDataString(folderName);
|
||||||
var clean = folderName.Replace('_', ' ').Trim();
|
var clean = unescaped.Replace('_', ' ').Trim();
|
||||||
|
clean = System.Text.RegularExpressions.Regex.Replace(clean, @"\s+", " ");
|
||||||
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(clean);
|
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(clean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class RadioStateService : IDisposable
|
|||||||
public double CurrentTime { get; private set; } = 0;
|
public double CurrentTime { get; private set; } = 0;
|
||||||
public double Duration { get; private set; } = 0;
|
public double Duration { get; private set; } = 0;
|
||||||
public string CurrentLyrics { get; private set; } = string.Empty;
|
public string CurrentLyrics { get; private set; } = string.Empty;
|
||||||
|
public bool IsAutoScrollEnabled { get; private set; } = true;
|
||||||
|
|
||||||
public RadioStateService(IJSRuntime js, RadioLibraryService library)
|
public RadioStateService(IJSRuntime js, RadioLibraryService library)
|
||||||
{
|
{
|
||||||
@@ -223,11 +224,14 @@ public class RadioStateService : IDisposable
|
|||||||
|
|
||||||
CurrentTrackIndex = (CurrentTrackIndex + 1) % TunedStation.Tracks.Count;
|
CurrentTrackIndex = (CurrentTrackIndex + 1) % TunedStation.Tracks.Count;
|
||||||
CurrentTime = 0;
|
CurrentTime = 0;
|
||||||
await LoadTrackDataAsync();
|
if (IsPoweredOn && IsTuned)
|
||||||
if (IsPoweredOn && IsPlaying)
|
|
||||||
{
|
{
|
||||||
await PlayCurrentTrackAsync();
|
await PlayCurrentTrackAsync();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await LoadTrackDataAsync();
|
||||||
|
}
|
||||||
NotifyStateChanged();
|
NotifyStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,11 +242,14 @@ public class RadioStateService : IDisposable
|
|||||||
|
|
||||||
CurrentTrackIndex = (CurrentTrackIndex - 1 + TunedStation.Tracks.Count) % TunedStation.Tracks.Count;
|
CurrentTrackIndex = (CurrentTrackIndex - 1 + TunedStation.Tracks.Count) % TunedStation.Tracks.Count;
|
||||||
CurrentTime = 0;
|
CurrentTime = 0;
|
||||||
await LoadTrackDataAsync();
|
if (IsPoweredOn && IsTuned)
|
||||||
if (IsPoweredOn && IsPlaying)
|
|
||||||
{
|
{
|
||||||
await PlayCurrentTrackAsync();
|
await PlayCurrentTrackAsync();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await LoadTrackDataAsync();
|
||||||
|
}
|
||||||
NotifyStateChanged();
|
NotifyStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +284,20 @@ public class RadioStateService : IDisposable
|
|||||||
NotifyStateChanged();
|
NotifyStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ToggleAutoScrollAsync()
|
||||||
|
{
|
||||||
|
IsAutoScrollEnabled = !IsAutoScrollEnabled;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _js.InvokeVoidAsync("retroRadio.setAutoScroll", IsAutoScrollEnabled);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"AutoScroll toggle error: {ex.Message}");
|
||||||
|
}
|
||||||
|
NotifyStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public async Task SeekAsync(double seconds)
|
public async Task SeekAsync(double seconds)
|
||||||
{
|
{
|
||||||
@@ -358,7 +379,19 @@ public class RadioStateService : IDisposable
|
|||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public async Task OnTrackEnded()
|
public async Task OnTrackEnded()
|
||||||
{
|
{
|
||||||
await NextTrackAsync();
|
if (!IsPoweredOn || TunedStation == null || TunedStation.Tracks.Count == 0) return;
|
||||||
|
|
||||||
|
if (TunedStation.Tracks.Count == 1)
|
||||||
|
{
|
||||||
|
// Single track station: seamless loop
|
||||||
|
CurrentTime = 0;
|
||||||
|
await PlayCurrentTrackAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Multiple tracks: auto advance to next track
|
||||||
|
await NextTrackAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ html, body {
|
|||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 155px;
|
height: 235px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
@@ -625,6 +625,74 @@ html, body {
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Song Progress Indicator & Scrubber */
|
||||||
|
.vfd-progress-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 12px;
|
||||||
|
margin: 2px 0 6px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-progress-track {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 6px;
|
||||||
|
background: #040e09;
|
||||||
|
border: 1px solid #143525;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #00f0ff 0%, #33ffaa 70%, #ffaa00 100%);
|
||||||
|
box-shadow: 0 0 8px rgba(51, 255, 170, 0.7);
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: width 0.08s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-progress-thumb {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
width: 6px;
|
||||||
|
height: 11px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #ffaa00;
|
||||||
|
border-radius: 2px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
box-shadow: 0 0 6px #ffaa00, 0 0 10px #00f0ff;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
transition: left 0.08s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-progress-slider {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-progress-wrapper:hover .vfd-progress-track {
|
||||||
|
border-color: #33ffaa;
|
||||||
|
box-shadow: 0 0 6px rgba(51, 255, 170, 0.3), inset 0 1px 3px rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-progress-wrapper.progress-disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.vfd-track-title {
|
.vfd-track-title {
|
||||||
font-family: var(--font-display);
|
font-family: var(--font-display);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
@@ -670,12 +738,55 @@ html, body {
|
|||||||
.vfd-panel-header {
|
.vfd-panel-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
font-size: 0.52rem;
|
font-size: 0.52rem;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
color: var(--color-vfd-dim);
|
color: var(--color-vfd-dim);
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vfd-autoscroll-toggle {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--color-vfd-dim);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.52rem;
|
||||||
|
padding: 1px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-autoscroll-toggle.autoscroll-on {
|
||||||
|
color: var(--color-accent-vfd);
|
||||||
|
border-color: var(--color-accent-vfd);
|
||||||
|
background: rgba(51, 255, 170, 0.12);
|
||||||
|
text-shadow: 0 0 5px var(--color-vfd-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-autoscroll-toggle.autoscroll-on .autoscroll-led {
|
||||||
|
color: #33ffaa;
|
||||||
|
text-shadow: 0 0 6px #33ffaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-autoscroll-toggle.autoscroll-off {
|
||||||
|
color: #6d7889;
|
||||||
|
border-color: #313845;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-autoscroll-toggle.autoscroll-off .autoscroll-led {
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vfd-autoscroll-toggle:hover {
|
||||||
|
filter: brightness(1.25);
|
||||||
|
border-color: var(--color-accent-amber);
|
||||||
|
}
|
||||||
|
|
||||||
.vfd-lyrics-container {
|
.vfd-lyrics-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<span class="dismiss">🗙</span>
|
<span class="dismiss">🗙</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/retro-radio.js?v=5"></script>
|
<script src="js/retro-radio.js?v=6"></script>
|
||||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ window.retroRadio = (() => {
|
|||||||
let isPowered = true;
|
let isPowered = true;
|
||||||
let currentVolume = 0.8;
|
let currentVolume = 0.8;
|
||||||
let currentSignal = 1.0;
|
let currentSignal = 1.0;
|
||||||
|
let isAutoScrollEnabled = true;
|
||||||
let isUserScrollingLyrics = false;
|
let isUserScrollingLyrics = false;
|
||||||
let userScrollTimeout = null;
|
let userScrollTimeout = null;
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@ window.retroRadio = (() => {
|
|||||||
if (audio.src !== absoluteUrl) {
|
if (audio.src !== absoluteUrl) {
|
||||||
audio.src = absoluteUrl;
|
audio.src = absoluteUrl;
|
||||||
}
|
}
|
||||||
|
audio.currentTime = startTime;
|
||||||
|
|
||||||
updateAudioLevels();
|
updateAudioLevels();
|
||||||
|
|
||||||
@@ -183,15 +185,27 @@ window.retroRadio = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateLyricsScroll(currentTime, duration) {
|
function updateLyricsScroll(currentTime, duration) {
|
||||||
if (isUserScrollingLyrics || !duration || duration <= 0) return;
|
if (!isAutoScrollEnabled || isUserScrollingLyrics || !duration || duration <= 0) return;
|
||||||
const container = document.querySelector(".vfd-lyrics-container");
|
const container = document.querySelector(".vfd-lyrics-container");
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const maxScroll = container.scrollHeight - container.clientHeight;
|
const maxScroll = container.scrollHeight - container.clientHeight;
|
||||||
if (maxScroll > 0) {
|
if (maxScroll <= 0) return;
|
||||||
const scrollRatio = Math.min(1.0, Math.max(0.0, currentTime / duration));
|
|
||||||
container.scrollTop = scrollRatio * maxScroll;
|
const INTRO_DELAY = 8.0; // Wait approx 8 seconds before text scrolling starts
|
||||||
|
if (currentTime <= INTRO_DELAY) {
|
||||||
|
container.scrollTop = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const effectiveDuration = Math.max(1.0, duration - INTRO_DELAY);
|
||||||
|
const scrollRatio = Math.min(1.0, Math.max(0.0, (currentTime - INTRO_DELAY) / effectiveDuration));
|
||||||
|
container.scrollTop = scrollRatio * maxScroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAutoScroll(enabled) {
|
||||||
|
isAutoScrollEnabled = !!enabled;
|
||||||
|
console.log("[AI Radio] Auto-scroll enabled:", isAutoScrollEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupLyricsScrollListeners() {
|
function setupLyricsScrollListeners() {
|
||||||
@@ -252,6 +266,7 @@ window.retroRadio = (() => {
|
|||||||
pauseTrack,
|
pauseTrack,
|
||||||
seek,
|
seek,
|
||||||
fadeToStatic,
|
fadeToStatic,
|
||||||
updateTrackInfo
|
updateTrackInfo,
|
||||||
|
setAutoScroll
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user