changes
This commit is contained in:
@@ -17,7 +17,7 @@ public class RadioLibraryService
|
||||
|
||||
public async Task<List<RadioStation>> LoadStationsAsync()
|
||||
{
|
||||
var folders = AudioCatalog.Folders;
|
||||
var folders = await DiscoverFoldersAsync();
|
||||
var loaded = new List<RadioStation>();
|
||||
int index = 0;
|
||||
|
||||
@@ -31,6 +31,129 @@ public class RadioLibraryService
|
||||
return _stations;
|
||||
}
|
||||
|
||||
private async Task<List<string>> DiscoverFoldersAsync()
|
||||
{
|
||||
var folders = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 1. Try querying IIS / server directory listing for audio/
|
||||
try
|
||||
{
|
||||
var response = await _http.GetAsync("audio/");
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var html = await response.Content.ReadAsStringAsync();
|
||||
if (html.Contains("<html", StringComparison.OrdinalIgnoreCase) || html.Contains("<a ", StringComparison.OrdinalIgnoreCase) || html.Contains("<A ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var discovered = ParseDirectoryListing(html);
|
||||
foreach (var f in discovered)
|
||||
{
|
||||
folders.Add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Directory browsing might be disabled or unavailable
|
||||
}
|
||||
|
||||
// 2. Merge with compiled AudioCatalog
|
||||
foreach (var f in AudioCatalog.Folders)
|
||||
{
|
||||
folders.Add(f);
|
||||
}
|
||||
|
||||
return folders.ToList();
|
||||
}
|
||||
|
||||
private async Task<List<string>> DiscoverTracksAsync(string folder)
|
||||
{
|
||||
var tracks = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// 1. Try querying IIS / server directory listing for audio/{folder}/
|
||||
try
|
||||
{
|
||||
var response = await _http.GetAsync($"audio/{folder}/");
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var html = await response.Content.ReadAsStringAsync();
|
||||
if (html.Contains("<html", StringComparison.OrdinalIgnoreCase) || html.Contains("<a ", StringComparison.OrdinalIgnoreCase) || html.Contains("<A ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var discovered = ParseAudioFilesFromHtml(html);
|
||||
foreach (var t in discovered)
|
||||
{
|
||||
tracks.Add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Directory browsing might be disabled or unavailable
|
||||
}
|
||||
|
||||
// 2. Merge with compiled AudioCatalog
|
||||
if (AudioCatalog.FolderTracks.TryGetValue(folder, out var compiledTracks) && compiledTracks != null)
|
||||
{
|
||||
foreach (var t in compiledTracks)
|
||||
{
|
||||
tracks.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate tracks by base name, preferring .mp3 over .wav
|
||||
var audioExtOrder = new[] { ".mp3", ".ogg", ".flac", ".m4a", ".wav", ".aac" };
|
||||
return tracks
|
||||
.GroupBy(f => Path.GetFileNameWithoutExtension(f), StringComparer.OrdinalIgnoreCase)
|
||||
.Select(g => g.OrderBy(f =>
|
||||
{
|
||||
var ext = Path.GetExtension(f).ToLowerInvariant();
|
||||
var idx = Array.IndexOf(audioExtOrder, ext);
|
||||
return idx >= 0 ? idx : 99;
|
||||
}).First())
|
||||
.OrderBy(t => t)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private List<string> ParseDirectoryListing(string html)
|
||||
{
|
||||
var list = new List<string>();
|
||||
var matches = System.Text.RegularExpressions.Regex.Matches(html, @"href\s*=\s*[""']([^""'#?]+)[""']", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
foreach (System.Text.RegularExpressions.Match m in matches)
|
||||
{
|
||||
var href = m.Groups[1].Value.Trim();
|
||||
href = href.TrimEnd('/');
|
||||
var name = Path.GetFileName(href);
|
||||
if (string.IsNullOrWhiteSpace(name) || name.StartsWith(".") || name.Equals("audio", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var ext = Path.GetExtension(name);
|
||||
if (!string.IsNullOrEmpty(ext))
|
||||
continue;
|
||||
|
||||
list.Add(name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<string> ParseAudioFilesFromHtml(string html)
|
||||
{
|
||||
var list = new List<string>();
|
||||
var validExts = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".mp3", ".wav", ".ogg", ".flac", ".m4a", ".aac" };
|
||||
var matches = System.Text.RegularExpressions.Regex.Matches(html, @"href\s*=\s*[""']([^""'#?]+)[""']", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
foreach (System.Text.RegularExpressions.Match m in matches)
|
||||
{
|
||||
var href = m.Groups[1].Value.Trim();
|
||||
var name = Path.GetFileName(href);
|
||||
var ext = Path.GetExtension(name);
|
||||
if (validExts.Contains(ext))
|
||||
{
|
||||
list.Add(name);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private async Task<RadioStation> LoadStationFromFolderAsync(string folder, int index)
|
||||
{
|
||||
var formattedName = FormatStationName(folder);
|
||||
@@ -61,14 +184,12 @@ public class RadioLibraryService
|
||||
}
|
||||
|
||||
// Dynamically discovered tracks from audio folder
|
||||
if (AudioCatalog.FolderTracks.TryGetValue(folder, out var fileNames) && fileNames != null && fileNames.Length > 0)
|
||||
{
|
||||
foreach (var fileName in fileNames)
|
||||
var tracks = await DiscoverTracksAsync(folder);
|
||||
foreach (var fileName in tracks)
|
||||
{
|
||||
var track = BuildTrack(folder, fileName, station.DefaultCoverUrl);
|
||||
station.Tracks.Add(track);
|
||||
}
|
||||
}
|
||||
|
||||
// If no tracks found, add a fallback track entry
|
||||
if (station.Tracks.Count == 0)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,43 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#181a24" />
|
||||
<stop offset="100%" stop-color="#08090d" />
|
||||
</linearGradient>
|
||||
<linearGradient id="neon" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#ff007f" />
|
||||
<stop offset="50%" stop-color="#00f0ff" />
|
||||
<stop offset="100%" stop-color="#39ff14" />
|
||||
</linearGradient>
|
||||
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feGaussianBlur stdDeviation="1.2" result="blur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Radio Body -->
|
||||
<rect x="2" y="2" width="60" height="60" rx="12" fill="url(#bg)" stroke="#00f0ff" stroke-width="2.5" />
|
||||
|
||||
<!-- Tuning Scale Window -->
|
||||
<rect x="8" y="10" width="48" height="18" rx="4" fill="#040a12" stroke="#ff007f" stroke-width="1.5" />
|
||||
<!-- Scale markings -->
|
||||
<line x1="14" y1="14" x2="14" y2="20" stroke="#00f0ff" stroke-width="1.2" opacity="0.9" />
|
||||
<line x1="20" y1="16" x2="20" y2="20" stroke="#00f0ff" stroke-width="1" opacity="0.6" />
|
||||
<line x1="26" y1="14" x2="26" y2="20" stroke="#00f0ff" stroke-width="1.2" opacity="0.9" />
|
||||
<line x1="32" y1="12" x2="32" y2="24" stroke="#ff007f" stroke-width="2.2" filter="url(#glow)" />
|
||||
<line x1="38" y1="14" x2="38" y2="20" stroke="#00f0ff" stroke-width="1.2" opacity="0.9" />
|
||||
<line x1="44" y1="16" x2="44" y2="20" stroke="#00f0ff" stroke-width="1" opacity="0.6" />
|
||||
<line x1="50" y1="14" x2="50" y2="20" stroke="#00f0ff" stroke-width="1.2" opacity="0.9" />
|
||||
|
||||
<!-- Equalizer / Speaker Bars -->
|
||||
<rect x="9" y="34" width="5" height="18" rx="2" fill="#00f0ff" />
|
||||
<rect x="17" y="40" width="5" height="12" rx="2" fill="#39ff14" />
|
||||
<rect x="25" y="32" width="5" height="20" rx="2" fill="#ff007f" filter="url(#glow)" />
|
||||
|
||||
<!-- Big Tuning Knob -->
|
||||
<circle cx="45" cy="44" r="10" fill="#12151f" stroke="#00f0ff" stroke-width="2" />
|
||||
<circle cx="45" cy="44" r="3.5" fill="#ff007f" filter="url(#glow)" />
|
||||
<line x1="45" y1="36" x2="45" y2="40" stroke="#00f0ff" stroke-width="2" stroke-linecap="round" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -12,7 +12,9 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Rajdhani:wght@500;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/app.css" />
|
||||
<link rel="stylesheet" href="css/radio.css" />
|
||||
<link rel="icon" type="image/png" href="icon-192.png" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="icon-192.png" />
|
||||
<script type="importmap"></script>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<!-- Allow static content file extensions and define explicit MIME types -->
|
||||
<staticContent>
|
||||
<!-- WebAssembly & Blazor Runtime files -->
|
||||
<remove fileExtension=".wasm" />
|
||||
<mimeMap fileExtension=".wasm" mimeType="application/wasm" />
|
||||
|
||||
<remove fileExtension=".dll" />
|
||||
<mimeMap fileExtension=".dll" mimeType="application/octet-stream" />
|
||||
|
||||
<remove fileExtension=".pdb" />
|
||||
<mimeMap fileExtension=".pdb" mimeType="application/octet-stream" />
|
||||
|
||||
<remove fileExtension=".dat" />
|
||||
<mimeMap fileExtension=".dat" mimeType="application/octet-stream" />
|
||||
|
||||
<remove fileExtension=".blat" />
|
||||
<mimeMap fileExtension=".blat" mimeType="application/octet-stream" />
|
||||
|
||||
<remove fileExtension=".json" />
|
||||
<mimeMap fileExtension=".json" mimeType="application/json" />
|
||||
|
||||
<remove fileExtension=".jsonl" />
|
||||
<mimeMap fileExtension=".jsonl" mimeType="application/x-jsonlines" />
|
||||
|
||||
<remove fileExtension=".webmanifest" />
|
||||
<mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
|
||||
|
||||
<!-- Audio & Media files -->
|
||||
<remove fileExtension=".mp3" />
|
||||
<mimeMap fileExtension=".mp3" mimeType="audio/mpeg" />
|
||||
|
||||
<remove fileExtension=".wav" />
|
||||
<mimeMap fileExtension=".wav" mimeType="audio/wav" />
|
||||
|
||||
<remove fileExtension=".ogg" />
|
||||
<mimeMap fileExtension=".ogg" mimeType="audio/ogg" />
|
||||
|
||||
<remove fileExtension=".m4a" />
|
||||
<mimeMap fileExtension=".m4a" mimeType="audio/mp4" />
|
||||
|
||||
<remove fileExtension=".flac" />
|
||||
<mimeMap fileExtension=".flac" mimeType="audio/flac" />
|
||||
|
||||
<remove fileExtension=".aac" />
|
||||
<mimeMap fileExtension=".aac" mimeType="audio/aac" />
|
||||
|
||||
<!-- Text, Lyrics & Station Metadata -->
|
||||
<remove fileExtension=".txt" />
|
||||
<mimeMap fileExtension=".txt" mimeType="text/plain; charset=utf-8" />
|
||||
|
||||
<remove fileExtension=".md" />
|
||||
<mimeMap fileExtension=".md" mimeType="text/markdown; charset=utf-8" />
|
||||
|
||||
<!-- Fonts & Images -->
|
||||
<remove fileExtension=".svg" />
|
||||
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
|
||||
|
||||
<remove fileExtension=".woff" />
|
||||
<mimeMap fileExtension=".woff" mimeType="font/woff" />
|
||||
|
||||
<remove fileExtension=".woff2" />
|
||||
<mimeMap fileExtension=".woff2" mimeType="font/woff2" />
|
||||
|
||||
<remove fileExtension=".ttf" />
|
||||
<mimeMap fileExtension=".ttf" mimeType="font/ttf" />
|
||||
|
||||
<remove fileExtension=".otf" />
|
||||
<mimeMap fileExtension=".otf" mimeType="font/otf" />
|
||||
|
||||
<remove fileExtension=".webp" />
|
||||
<mimeMap fileExtension=".webp" mimeType="image/webp" />
|
||||
</staticContent>
|
||||
|
||||
<!-- Request Filtering: Ensure IIS does not block downloading .dll or .wasm files -->
|
||||
<security>
|
||||
<requestFiltering>
|
||||
<fileExtensions allowUnlisted="true">
|
||||
<remove fileExtension=".dll" />
|
||||
<add fileExtension=".dll" allowed="true" />
|
||||
<remove fileExtension=".wasm" />
|
||||
<add fileExtension=".wasm" allowed="true" />
|
||||
<remove fileExtension=".dat" />
|
||||
<add fileExtension=".dat" allowed="true" />
|
||||
<remove fileExtension=".pdb" />
|
||||
<add fileExtension=".pdb" allowed="true" />
|
||||
</fileExtensions>
|
||||
</requestFiltering>
|
||||
</security>
|
||||
|
||||
<!-- SPA URL Rewrite: Route all non-file/non-directory requests to index.html -->
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="SPA fallback routing" stopProcessing="true">
|
||||
<match url=".*" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="index.html" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
|
||||
<!-- Pass-through for custom errors so Blazor client gets proper HTTP response codes -->
|
||||
<httpErrors errorMode="Custom" existingResponse="PassThrough" />
|
||||
</system.webServer>
|
||||
|
||||
<!-- Enable directory browsing for audio folder so newly added MP3s / folders are dynamically listed -->
|
||||
<location path="audio">
|
||||
<system.webServer>
|
||||
<directoryBrowse enabled="true" />
|
||||
</system.webServer>
|
||||
</location>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user