12 Commits
1.1.0 ... Beta

Author SHA1 Message Date
minster586
34c80bda7b Set Time Clock 2026-07-01 22:37:18 -04:00
ea86ff00db Update README.md 2026-04-13 00:49:27 -04:00
e73176f806 Added some endpoint
I added some endpoints but they were already in there but I wanted them documented
2026-04-13 00:46:48 -04:00
ca20267307 Added new information for web server 2026-04-13 00:42:27 -04:00
minster586
a31bcc2971 Updated version and finally got web server running 2026-04-13 00:21:18 -04:00
minster586
761b382364 Made some minor changes and set in a widget for the web 2026-04-12 23:45:04 -04:00
minster586
79268a61ff update version 2025-10-10 04:28:22 -04:00
minster586
6c6ee120ce Made a few design changes 2025-10-10 04:24:49 -04:00
minster586
d07c32556b Trying to work on the marquee 2025-10-10 03:58:19 -04:00
minster586
7c1da23da0 I have moved the load profile to the main window 2025-10-10 03:18:11 -04:00
minster586
9fecd18519 uploaded screenshots 2025-09-08 17:52:06 -04:00
minster586
f5708c6dc6 Updated the readme 2025-09-08 17:19:38 -04:00
16 changed files with 1498 additions and 174 deletions

View File

@@ -1,40 +1,59 @@
# RadioDJViewer # RadioDJViewer v1.8.0
RadioDJViewer is a Windows Forms application for displaying song information and album art from a RadioDJ REST API. It is designed for use with RadioDJ automation software and provides a simple interface for viewing and exporting currently playing track details. **RadioDJViewer** is a high-performance Windows application designed to bridge the gap between RadioDJ and your broadcast visuals. It fetches live "Now Playing" data from the RadioDJ REST API and serves it simultaneously to physical local files and a built-in network web server.
## Features ## 🚀 New in v1.8.0: The Web Update
- Connects to RadioDJ REST API to acquire song info * **Integrated Web Server:** Hosts a live, high-performance web widget for OBS Browser Sources.
- Displays title, artist, and album (with scrolling marquee for long text) * **Tri-State Status Indicator:**
- Shows album art or a fallback image if none is available * 🔴 **Red:** Disconnected from RadioDJ API.
- Exports title, artist, album, and album art to output folder * 🟢 **Green:** Connected to API (Web Server Off).
- Supports multiple profiles for different RadioDJ instances * 🔵 **Blue:** Full Broadcast Mode (API Connected + Web Server Live).
- Auto-regenerates missing `profiles.json` and fallback image * **Memory-Caching:** Data is served to the web widget directly from RAM for zero-latency updates and reduced disk wear.
* **Network-Wide Access:** View your "Now Playing" widget on any device (Phones, Tablets, or second PCs) on your local network.
## Requirements ## 🛠 Features
- Windows 7 or later * Connects to RadioDJ REST API to acquire real-time song info.
- .NET Framework 4.7.2 * Displays title, artist, and album (with scrolling marquee for long text).
- RadioDJ REST API running and accessible * Dual-Output: Exports `.txt` and `.png` files for legacy GDI+ sources while hosting the web stream.
* Supports multiple profiles for different RadioDJ instances.
* Auto-regenerates missing `profiles.json` and fallback images.
## Installation ## 🌐 Web API Endpoints
1. Download the release ZIP and extract all files to a folder. Once the server is active (Blue Status), you can access the following data URLs locally or over your network:
2. Ensure the following files are present:
- `RadioDJViewer.exe`
- `Newtonsoft.Json.dll`
- `RadioDJViewer.exe.config`
- (Optional) `default.jpg` and `profiles.json` (will be auto-generated if missing)
3. Run `RadioDJViewer.exe`.
## Usage | Resource | URL (Localhost) | Purpose |
1. Configure a profile with the REST API URL, output folder, and image settings. | :--- | :--- | :--- |
2. Click Connect to start polling the API and display song info. | **Main Widget** | `http://127.0.0.1:8181/` | Loads the full HTML/CSS/JS widget for OBS. |
3. Output files will be saved in the selected output folder. | **Song Title** | `http://127.0.0.1:8181/title.txt` | Returns the raw text of the current song. |
4. If album art is missing, a fallback image will be used. | **Artist Name** | `http://127.0.0.1:8181/artist.txt` | Returns the raw text of the current artist. |
| **Album Art** | `http://127.0.0.1:8181/Album-Art.png` | Serves the current album art image bytes. |
## License *Note: Replace `127.0.0.1` with your computer's local IP address to access these from other devices.*
This project is licensed under the MIT License.
## Repository ## 📋 Requirements
[https://git.smartcraft.me/minster586/RadioDJViewer](https://git.smartcraft.me/minster586/RadioDJViewer) * Windows 7 or later.
* .NET Framework 4.7.2.
* RadioDJ with the **REST Server Plugin** enabled and configured.
## 🔧 Installation & Setup
1. **Download & Extract:** Run the `RadioDJViewer.exe`.
2. **Run as Administrator:** This is required for the Web Server to bind to your network port.
3. **Configure API:** Enter your RadioDJ IP, Port, and Auth Key in the settings.
4. **Set Output:** Choose a folder for your physical text and image files (e.g., `H:/RDJ-Output/`).
5. **Connect:** Click **Connect**. Your status box should turn **Blue** when everything is active.
## 🌐 Network & Firewall Setup
To allow other devices to see the widget, you must open the port in the Windows Firewall:
1. Open **Windows Firewall with Advanced Security**.
2. Create a **New Inbound Rule** -> **Port** -> **TCP**.
3. Enter Port **8181** (or your chosen port) and select **Allow the connection**.
## 📝 Credits
This program was developed as a learning journey into C# and .NET, with collaborative assistance from **Microsoft Copilot** and **Google Gemini** to refine the API parsing and web server logic.
## 🔗 Links
* **Repository:** [https://git.smartcraft.me/minster586/RadioDJViewer](https://git.smartcraft.me/minster586/RadioDJViewer)
* **Contact/Stream:** [https://links.smartcraft.me/@minster586](https://links.smartcraft.me/@minster586)
--- ---
For questions or issues, please open an issue on the repository or contact the maintainer. For questions or issues, please open an issue on the repository or contact the maintainer.

View File

@@ -0,0 +1,41 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace RadioDJViewer
{
public static class EmbeddedResourceHelper
{
/// <summary>
/// Reads an embedded resource's text content by matching the resource name ending with the provided file name.
/// Example: call Read("index.html") to load a resource named "RadioDJViewer.Web.index.html" or similar.
/// </summary>
public static string Read(string fileName)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentNullException(nameof(fileName));
var asm = Assembly.GetExecutingAssembly();
var resourceName = asm.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith(fileName, StringComparison.OrdinalIgnoreCase));
if (resourceName == null)
{
var names = string.Join(", ", asm.GetManifestResourceNames());
throw new FileNotFoundException($"Embedded resource '{fileName}' not found. Available resources: {names}");
}
using (var stream = asm.GetManifestResourceStream(resourceName))
{
if (stream == null)
throw new FileNotFoundException($"Resource stream '{resourceName}' is null.");
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
}
}

View File

@@ -43,6 +43,8 @@
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.groupBox1 = new System.Windows.Forms.GroupBox(); this.groupBox1 = new System.Windows.Forms.GroupBox();
this.label8 = new System.Windows.Forms.Label();
this.label7 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label(); this.label6 = new System.Windows.Forms.Label();
this.label5 = new System.Windows.Forms.Label(); this.label5 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label(); this.label4 = new System.Windows.Forms.Label();
@@ -53,6 +55,12 @@
this.button2 = new System.Windows.Forms.Button(); this.button2 = new System.Windows.Forms.Button();
this.uicolor = new System.Windows.Forms.Button(); this.uicolor = new System.Windows.Forms.Button();
this.pictureBox1 = new System.Windows.Forms.PictureBox(); this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.profile_drop_box = new System.Windows.Forms.ComboBox();
this.profile_label = new System.Windows.Forms.Label();
this.load_profile_button = new System.Windows.Forms.Button();
this.time_label = new System.Windows.Forms.Label();
this.label9 = new System.Windows.Forms.Label();
this.time_data_label = new System.Windows.Forms.Label();
this.statusStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout();
this.menuStrip2.SuspendLayout(); this.menuStrip2.SuspendLayout();
this.groupBox1.SuspendLayout(); this.groupBox1.SuspendLayout();
@@ -66,9 +74,9 @@
this.toolStripStatusLabel2, this.toolStripStatusLabel2,
this.toolStripStatusLabel4, this.toolStripStatusLabel4,
this.toolStripStatusLabel3}); this.toolStripStatusLabel3});
this.statusStrip1.Location = new System.Drawing.Point(0, 268); this.statusStrip1.Location = new System.Drawing.Point(0, 362);
this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(612, 25); this.statusStrip1.Size = new System.Drawing.Size(612, 26);
this.statusStrip1.TabIndex = 0; this.statusStrip1.TabIndex = 0;
this.statusStrip1.Text = "statusStrip1"; this.statusStrip1.Text = "statusStrip1";
// //
@@ -76,20 +84,22 @@
// //
this.toolStripStatusLabel1.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.toolStripStatusLabel1.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
this.toolStripStatusLabel1.Size = new System.Drawing.Size(57, 20); this.toolStripStatusLabel1.Size = new System.Drawing.Size(64, 21);
this.toolStripStatusLabel1.Text = "Status:"; this.toolStripStatusLabel1.Text = "Status:";
// //
// toolStripStatusLabel2 // toolStripStatusLabel2
// //
this.toolStripStatusLabel2.Image = ((System.Drawing.Image)(resources.GetObject("toolStripStatusLabel2.Image"))); this.toolStripStatusLabel2.BackColor = System.Drawing.Color.Red;
this.toolStripStatusLabel2.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.toolStripStatusLabel2.Name = "toolStripStatusLabel2"; this.toolStripStatusLabel2.Name = "toolStripStatusLabel2";
this.toolStripStatusLabel2.Size = new System.Drawing.Size(16, 20); this.toolStripStatusLabel2.Size = new System.Drawing.Size(12, 21);
this.toolStripStatusLabel2.Text = " ";
// //
// toolStripStatusLabel4 // toolStripStatusLabel4
// //
this.toolStripStatusLabel4.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.toolStripStatusLabel4.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.toolStripStatusLabel4.Name = "toolStripStatusLabel4"; this.toolStripStatusLabel4.Name = "toolStripStatusLabel4";
this.toolStripStatusLabel4.Size = new System.Drawing.Size(262, 20); this.toolStripStatusLabel4.Size = new System.Drawing.Size(260, 21);
this.toolStripStatusLabel4.Spring = true; this.toolStripStatusLabel4.Spring = true;
this.toolStripStatusLabel4.Text = "Profile:"; this.toolStripStatusLabel4.Text = "Profile:";
this.toolStripStatusLabel4.Click += new System.EventHandler(this.toolStripStatusLabel4_Click); this.toolStripStatusLabel4.Click += new System.EventHandler(this.toolStripStatusLabel4_Click);
@@ -97,7 +107,7 @@
// toolStripStatusLabel3 // toolStripStatusLabel3
// //
this.toolStripStatusLabel3.Name = "toolStripStatusLabel3"; this.toolStripStatusLabel3.Name = "toolStripStatusLabel3";
this.toolStripStatusLabel3.Size = new System.Drawing.Size(262, 20); this.toolStripStatusLabel3.Size = new System.Drawing.Size(260, 21);
this.toolStripStatusLabel3.Spring = true; this.toolStripStatusLabel3.Spring = true;
this.toolStripStatusLabel3.Text = "Song Changed Updateing"; this.toolStripStatusLabel3.Text = "Song Changed Updateing";
this.toolStripStatusLabel3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; this.toolStripStatusLabel3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
@@ -109,7 +119,7 @@
this.aboutToolStripMenuItem}); this.aboutToolStripMenuItem});
this.menuStrip2.Location = new System.Drawing.Point(0, 0); this.menuStrip2.Location = new System.Drawing.Point(0, 0);
this.menuStrip2.Name = "menuStrip2"; this.menuStrip2.Name = "menuStrip2";
this.menuStrip2.Size = new System.Drawing.Size(612, 24); this.menuStrip2.Size = new System.Drawing.Size(612, 25);
this.menuStrip2.TabIndex = 2; this.menuStrip2.TabIndex = 2;
this.menuStrip2.Text = "menuStrip2"; this.menuStrip2.Text = "menuStrip2";
// //
@@ -120,7 +130,7 @@
this.loggingToolStripMenuItem, this.loggingToolStripMenuItem,
this.exitToolStripMenuItem}); this.exitToolStripMenuItem});
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); this.fileToolStripMenuItem.Size = new System.Drawing.Size(41, 21);
this.fileToolStripMenuItem.Text = "File"; this.fileToolStripMenuItem.Text = "File";
// //
// restAPISettingToolStripMenuItem // restAPISettingToolStripMenuItem
@@ -137,19 +147,19 @@
this.offToolStripMenuItem}); this.offToolStripMenuItem});
this.loggingToolStripMenuItem.Enabled = false; this.loggingToolStripMenuItem.Enabled = false;
this.loggingToolStripMenuItem.Name = "loggingToolStripMenuItem"; this.loggingToolStripMenuItem.Name = "loggingToolStripMenuItem";
this.loggingToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.loggingToolStripMenuItem.Size = new System.Drawing.Size(118, 22);
this.loggingToolStripMenuItem.Text = "Logging"; this.loggingToolStripMenuItem.Text = "Logging";
// //
// onToolStripMenuItem // onToolStripMenuItem
// //
this.onToolStripMenuItem.Name = "onToolStripMenuItem"; this.onToolStripMenuItem.Name = "onToolStripMenuItem";
this.onToolStripMenuItem.Size = new System.Drawing.Size(91, 22); this.onToolStripMenuItem.Size = new System.Drawing.Size(98, 22);
this.onToolStripMenuItem.Text = "On"; this.onToolStripMenuItem.Text = "On";
// //
// offToolStripMenuItem // offToolStripMenuItem
// //
this.offToolStripMenuItem.Name = "offToolStripMenuItem"; this.offToolStripMenuItem.Name = "offToolStripMenuItem";
this.offToolStripMenuItem.Size = new System.Drawing.Size(91, 22); this.offToolStripMenuItem.Size = new System.Drawing.Size(98, 22);
this.offToolStripMenuItem.Text = "Off"; this.offToolStripMenuItem.Text = "Off";
// //
// exitToolStripMenuItem // exitToolStripMenuItem
@@ -162,12 +172,17 @@
// aboutToolStripMenuItem // aboutToolStripMenuItem
// //
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(52, 20); this.aboutToolStripMenuItem.Size = new System.Drawing.Size(52, 21);
this.aboutToolStripMenuItem.Text = "About"; this.aboutToolStripMenuItem.Text = "About";
this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
// //
// groupBox1 // groupBox1
// //
this.groupBox1.Controls.Add(this.time_data_label);
this.groupBox1.Controls.Add(this.label9);
this.groupBox1.Controls.Add(this.time_label);
this.groupBox1.Controls.Add(this.label8);
this.groupBox1.Controls.Add(this.label7);
this.groupBox1.Controls.Add(this.label6); this.groupBox1.Controls.Add(this.label6);
this.groupBox1.Controls.Add(this.label5); this.groupBox1.Controls.Add(this.label5);
this.groupBox1.Controls.Add(this.label4); this.groupBox1.Controls.Add(this.label4);
@@ -177,68 +192,93 @@
this.groupBox1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.groupBox1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.groupBox1.Location = new System.Drawing.Point(199, 37); this.groupBox1.Location = new System.Drawing.Point(199, 37);
this.groupBox1.Name = "groupBox1"; this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(390, 173); this.groupBox1.Size = new System.Drawing.Size(390, 205);
this.groupBox1.TabIndex = 4; this.groupBox1.TabIndex = 4;
this.groupBox1.TabStop = false; this.groupBox1.TabStop = false;
this.groupBox1.Text = "Song Info"; this.groupBox1.Text = "Song Info";
// //
// label8
//
this.label8.AutoSize = true;
this.label8.Location = new System.Drawing.Point(20, 103);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(357, 16);
this.label8.TabIndex = 7;
this.label8.Text = "――――――――――――――――――――――――――――――――――――――――――――――――――";
this.label8.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
//
// label7
//
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(19, 61);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(357, 16);
this.label7.TabIndex = 6;
this.label7.Text = "――――――――――――――――――――――――――――――――――――――――――――――――――";
//
// label6 // label6
// //
this.label6.Location = new System.Drawing.Point(99, 129); this.label6.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label6.Location = new System.Drawing.Point(99, 121);
this.label6.Name = "label6"; this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(266, 17); this.label6.Size = new System.Drawing.Size(266, 17);
this.label6.TabIndex = 5; this.label6.TabIndex = 5;
this.label6.Text = "No album"; this.label6.Text = "No album";
this.label6.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
// //
// label5 // label5
// //
this.label5.Location = new System.Drawing.Point(99, 83); this.label5.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label5.Location = new System.Drawing.Point(99, 76);
this.label5.Name = "label5"; this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(266, 17); this.label5.Size = new System.Drawing.Size(266, 17);
this.label5.TabIndex = 4; this.label5.TabIndex = 4;
this.label5.Text = "No artist"; this.label5.Text = "No artist";
this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
// //
// label4 // label4
// //
this.label4.Location = new System.Drawing.Point(99, 39); this.label4.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label4.Location = new System.Drawing.Point(99, 33);
this.label4.Name = "label4"; this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(266, 16); this.label4.Size = new System.Drawing.Size(266, 16);
this.label4.TabIndex = 3; this.label4.TabIndex = 3;
this.label4.Text = "No title"; this.label4.Text = "No title";
this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
// //
// label3 // label3
// //
this.label3.AutoSize = true; this.label3.AutoSize = true;
this.label3.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, ((System.Drawing.FontStyle)((System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Underline))), System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.label3.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, ((System.Drawing.FontStyle)((System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Underline))), System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label3.Location = new System.Drawing.Point(23, 126); this.label3.Location = new System.Drawing.Point(23, 116);
this.label3.Name = "label3"; this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(64, 20); this.label3.Size = new System.Drawing.Size(76, 24);
this.label3.TabIndex = 2; this.label3.TabIndex = 2;
this.label3.Text = "Album:"; this.label3.Text = "Album:";
// //
// label2 // label2
// //
this.label2.AutoSize = true; this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, ((System.Drawing.FontStyle)((System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Underline))), System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.label2.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, ((System.Drawing.FontStyle)((System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Underline))), System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label2.Location = new System.Drawing.Point(23, 80); this.label2.Location = new System.Drawing.Point(23, 75);
this.label2.Name = "label2"; this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(57, 20); this.label2.Size = new System.Drawing.Size(62, 24);
this.label2.TabIndex = 1; this.label2.TabIndex = 1;
this.label2.Text = "Artist:"; this.label2.Text = "Artist:";
// //
// label1 // label1
// //
this.label1.AutoSize = true; this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, ((System.Drawing.FontStyle)((System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Underline))), System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, ((System.Drawing.FontStyle)((System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Underline))), System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label1.Location = new System.Drawing.Point(23, 35); this.label1.Location = new System.Drawing.Point(23, 29);
this.label1.Name = "label1"; this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(48, 20); this.label1.Size = new System.Drawing.Size(56, 24);
this.label1.TabIndex = 0; this.label1.TabIndex = 0;
this.label1.Text = "Title:"; this.label1.Text = "Title:";
// //
// button1 // button1
// //
this.button1.Location = new System.Drawing.Point(250, 216); this.button1.Location = new System.Drawing.Point(62, 281);
this.button1.Name = "button1"; this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(99, 42); this.button1.Size = new System.Drawing.Size(99, 42);
this.button1.TabIndex = 5; this.button1.TabIndex = 5;
@@ -248,7 +288,7 @@
// //
// button2 // button2
// //
this.button2.Location = new System.Drawing.Point(392, 216); this.button2.Location = new System.Drawing.Point(180, 281);
this.button2.Name = "button2"; this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(99, 42); this.button2.Size = new System.Drawing.Size(99, 42);
this.button2.TabIndex = 6; this.button2.TabIndex = 6;
@@ -259,7 +299,7 @@
// uicolor // uicolor
// //
this.uicolor.Image = global::RadioDJViewer.Properties.Resources.MdiWeatherNight; this.uicolor.Image = global::RadioDJViewer.Properties.Resources.MdiWeatherNight;
this.uicolor.Location = new System.Drawing.Point(13, 234); this.uicolor.Location = new System.Drawing.Point(572, 327);
this.uicolor.Name = "uicolor"; this.uicolor.Name = "uicolor";
this.uicolor.Size = new System.Drawing.Size(28, 23); this.uicolor.Size = new System.Drawing.Size(28, 23);
this.uicolor.TabIndex = 7; this.uicolor.TabIndex = 7;
@@ -267,17 +307,78 @@
// //
// pictureBox1 // pictureBox1
// //
this.pictureBox1.Location = new System.Drawing.Point(13, 37); this.pictureBox1.Location = new System.Drawing.Point(13, 69);
this.pictureBox1.Name = "pictureBox1"; this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(180, 173); this.pictureBox1.Size = new System.Drawing.Size(180, 173);
this.pictureBox1.TabIndex = 3; this.pictureBox1.TabIndex = 3;
this.pictureBox1.TabStop = false; this.pictureBox1.TabStop = false;
// //
// profile_drop_box
//
this.profile_drop_box.FormattingEnabled = true;
this.profile_drop_box.Location = new System.Drawing.Point(62, 329);
this.profile_drop_box.Name = "profile_drop_box";
this.profile_drop_box.Size = new System.Drawing.Size(217, 21);
this.profile_drop_box.TabIndex = 8;
//
// profile_label
//
this.profile_label.AutoSize = true;
this.profile_label.Location = new System.Drawing.Point(12, 332);
this.profile_label.Name = "profile_label";
this.profile_label.Size = new System.Drawing.Size(44, 13);
this.profile_label.TabIndex = 9;
this.profile_label.Text = "Profiles:";
this.profile_label.Click += new System.EventHandler(this.label7_Click);
//
// load_profile_button
//
this.load_profile_button.Location = new System.Drawing.Point(285, 327);
this.load_profile_button.Name = "load_profile_button";
this.load_profile_button.Size = new System.Drawing.Size(75, 23);
this.load_profile_button.TabIndex = 10;
this.load_profile_button.Text = "Load Profile";
this.load_profile_button.UseVisualStyleBackColor = true;
//
// time_label
//
this.time_label.AutoSize = true;
this.time_label.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F, ((System.Drawing.FontStyle)((System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Underline))), System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.time_label.Location = new System.Drawing.Point(20, 157);
this.time_label.Name = "time_label";
this.time_label.Size = new System.Drawing.Size(63, 24);
this.time_label.TabIndex = 8;
this.time_label.Text = "Time:";
this.time_label.Click += new System.EventHandler(this.label9_Click);
//
// label9
//
this.label9.AutoSize = true;
this.label9.Location = new System.Drawing.Point(17, 144);
this.label9.Name = "label9";
this.label9.Size = new System.Drawing.Size(357, 16);
this.label9.TabIndex = 9;
this.label9.Text = "――――――――――――――――――――――――――――――――――――――――――――――――――";
this.label9.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
//
// time_data_label
//
this.time_data_label.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.time_data_label.Location = new System.Drawing.Point(97, 160);
this.time_data_label.Name = "time_data_label";
this.time_data_label.Size = new System.Drawing.Size(266, 17);
this.time_data_label.TabIndex = 10;
this.time_data_label.Text = "No Data";
this.time_data_label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// Main // Main
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(612, 293); this.ClientSize = new System.Drawing.Size(612, 388);
this.Controls.Add(this.load_profile_button);
this.Controls.Add(this.profile_label);
this.Controls.Add(this.profile_drop_box);
this.Controls.Add(this.uicolor); this.Controls.Add(this.uicolor);
this.Controls.Add(this.button2); this.Controls.Add(this.button2);
this.Controls.Add(this.button1); this.Controls.Add(this.button1);
@@ -326,6 +427,14 @@
private System.Windows.Forms.ToolStripMenuItem onToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem onToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem offToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem offToolStripMenuItem;
private System.Windows.Forms.Button uicolor; private System.Windows.Forms.Button uicolor;
private System.Windows.Forms.ComboBox profile_drop_box;
private System.Windows.Forms.Label profile_label;
private System.Windows.Forms.Button load_profile_button;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.Label label8;
private System.Windows.Forms.Label time_label;
private System.Windows.Forms.Label label9;
private System.Windows.Forms.Label time_data_label;
} }
} }

View File

@@ -8,13 +8,21 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using System.Xml; using System.Xml;
using System.Threading;
using System.IO; using System.IO;
using System.Resources; using System.Resources;
using System.Globalization;
namespace RadioDJViewer namespace RadioDJViewer
{ {
public partial class Main : Form public partial class Main : Form
{ {
private string _currentTrackKey = string.Empty;
private string _playbackTrackKey = string.Empty;
private double _playbackElapsedSeconds = 0;
private double _playbackDurationSeconds = 0;
private bool _playbackPaused = true;
private DateTime _playbackBaseUtc = DateTime.UtcNow;
private string selectedFolderPath = string.Empty; private string selectedFolderPath = string.Empty;
private string outputFolderPath = string.Empty; private string outputFolderPath = string.Empty;
private bool isConnected = false; private bool isConnected = false;
@@ -23,9 +31,13 @@ namespace RadioDJViewer
private string defaultImagePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "default.jpg"); private string defaultImagePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "default.jpg");
private string mainImagesFolderPath = string.Empty; // New field for main images folder path private string mainImagesFolderPath = string.Empty; // New field for main images folder path
private Profile loadedProfile = null; // Field to store the loaded profile private Profile loadedProfile = null; // Field to store the loaded profile
private WidgetServer widgetServer = null; // Local widget HTTP server
private bool widgetRunning = false;
private CancellationTokenSource statusCts = null;
// Timer to poll REST API // Timer to poll REST API
private System.Windows.Forms.Timer apiTimer; private System.Windows.Forms.Timer apiTimer;
private System.Windows.Forms.Timer playbackTimer;
private string lastApiXml = null; private string lastApiXml = null;
private System.Windows.Forms.Timer marqueeTimerTitle; private System.Windows.Forms.Timer marqueeTimerTitle;
@@ -38,9 +50,9 @@ namespace RadioDJViewer
private string marqueeTextArtist = ""; private string marqueeTextArtist = "";
private string marqueeTextAlbum = ""; private string marqueeTextAlbum = "";
private Timer pauseTimerTitle; private System.Windows.Forms.Timer pauseTimerTitle;
private Timer pauseTimerArtist; private System.Windows.Forms.Timer pauseTimerArtist;
private Timer pauseTimerAlbum; private System.Windows.Forms.Timer pauseTimerAlbum;
private int marqueeScrollSpeed = 100; // Default to Medium private int marqueeScrollSpeed = 100; // Default to Medium
private int marqueePauseTime = 6000; // Default to 6 seconds private int marqueePauseTime = 6000; // Default to 6 seconds
@@ -58,6 +70,9 @@ namespace RadioDJViewer
apiTimer = new System.Windows.Forms.Timer(); apiTimer = new System.Windows.Forms.Timer();
apiTimer.Interval = 3000; // 3 seconds apiTimer.Interval = 3000; // 3 seconds
apiTimer.Tick += ApiTimer_Tick; apiTimer.Tick += ApiTimer_Tick;
playbackTimer = new System.Windows.Forms.Timer();
playbackTimer.Interval = 250;
playbackTimer.Tick += PlaybackTimer_Tick;
// Marquee timers for each label // Marquee timers for each label
marqueeTimerTitle = new System.Windows.Forms.Timer(); marqueeTimerTitle = new System.Windows.Forms.Timer();
marqueeTimerTitle.Interval = marqueeScrollSpeed; marqueeTimerTitle.Interval = marqueeScrollSpeed;
@@ -70,6 +85,8 @@ namespace RadioDJViewer
marqueeTimerAlbum.Tick += MarqueeTimerAlbum_Tick; marqueeTimerAlbum.Tick += MarqueeTimerAlbum_Tick;
// Wire up dark mode button // Wire up dark mode button
this.uicolor.Click += Uicolor_Click; this.uicolor.Click += Uicolor_Click;
this.load_profile_button.Click += load_profile_button_Click;
PopulateProfileDropBox();
UpdateTheme(); UpdateTheme();
// Auto-load profiles if profiles.json exists // Auto-load profiles if profiles.json exists
AutoLoadProfiles(); AutoLoadProfiles();
@@ -124,9 +141,28 @@ namespace RadioDJViewer
catch { } catch { }
} }
private int GetVisibleChars(Label label)
{
// Use the label's actual width and font to estimate visible characters
using (Graphics g = label.CreateGraphics())
{
// Use the full label width for measurement
SizeF size = g.MeasureString("W", label.Font);
int chars = (int)(label.Width / size.Width);
// If the label is single-line, ensure we use the full width
return Math.Max(chars, 1);
}
}
private void SetLabelTextFull(Label label, string text)
{
// Always set the full text, and let marquee logic handle scrolling
label.Text = text;
}
private void MarqueeTimerTitle_Tick(object sender, EventArgs e) private void MarqueeTimerTitle_Tick(object sender, EventArgs e)
{ {
int visibleChars = 30; // Adjust for label width int visibleChars = GetVisibleChars(label4);
if (marqueeTextTitle.Length > visibleChars) if (marqueeTextTitle.Length > visibleChars)
{ {
string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator; string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator;
@@ -140,7 +176,7 @@ namespace RadioDJViewer
marqueeTimerTitle.Stop(); marqueeTimerTitle.Stop();
if (pauseTimerTitle == null) if (pauseTimerTitle == null)
{ {
pauseTimerTitle = new Timer(); pauseTimerTitle = new System.Windows.Forms.Timer();
pauseTimerTitle.Tick += (s, args) => { pauseTimerTitle.Tick += (s, args) => {
pauseTimerTitle.Stop(); pauseTimerTitle.Stop();
marqueeTimerTitle.Start(); marqueeTimerTitle.Start();
@@ -152,13 +188,13 @@ namespace RadioDJViewer
} }
else else
{ {
label4.Text = marqueeTextTitle; SetLabelTextFull(label4, marqueeTextTitle);
} }
} }
private void MarqueeTimerArtist_Tick(object sender, EventArgs e) private void MarqueeTimerArtist_Tick(object sender, EventArgs e)
{ {
int visibleChars = 30; int visibleChars = GetVisibleChars(label5);
if (marqueeTextArtist.Length > visibleChars) if (marqueeTextArtist.Length > visibleChars)
{ {
string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator; string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator;
@@ -172,7 +208,7 @@ namespace RadioDJViewer
marqueeTimerArtist.Stop(); marqueeTimerArtist.Stop();
if (pauseTimerArtist == null) if (pauseTimerArtist == null)
{ {
pauseTimerArtist = new Timer(); pauseTimerArtist = new System.Windows.Forms.Timer();
pauseTimerArtist.Tick += (s, args) => { pauseTimerArtist.Tick += (s, args) => {
pauseTimerArtist.Stop(); pauseTimerArtist.Stop();
marqueeTimerArtist.Start(); marqueeTimerArtist.Start();
@@ -184,13 +220,13 @@ namespace RadioDJViewer
} }
else else
{ {
label5.Text = marqueeTextArtist; SetLabelTextFull(label5, marqueeTextArtist);
} }
} }
private void MarqueeTimerAlbum_Tick(object sender, EventArgs e) private void MarqueeTimerAlbum_Tick(object sender, EventArgs e)
{ {
int visibleChars = 30; int visibleChars = GetVisibleChars(label6);
if (marqueeTextAlbum.Length > visibleChars) if (marqueeTextAlbum.Length > visibleChars)
{ {
string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator; string separator = string.IsNullOrEmpty(marqueeSeparator) ? "" : marqueeSeparator;
@@ -204,7 +240,7 @@ namespace RadioDJViewer
marqueeTimerAlbum.Stop(); marqueeTimerAlbum.Stop();
if (pauseTimerAlbum == null) if (pauseTimerAlbum == null)
{ {
pauseTimerAlbum = new Timer(); pauseTimerAlbum = new System.Windows.Forms.Timer();
pauseTimerAlbum.Tick += (s, args) => { pauseTimerAlbum.Tick += (s, args) => {
pauseTimerAlbum.Stop(); pauseTimerAlbum.Stop();
marqueeTimerAlbum.Start(); marqueeTimerAlbum.Start();
@@ -216,7 +252,7 @@ namespace RadioDJViewer
} }
else else
{ {
label6.Text = marqueeTextAlbum; SetLabelTextFull(label6, marqueeTextAlbum);
} }
} }
@@ -249,11 +285,18 @@ namespace RadioDJViewer
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
var xml = await response.Content.ReadAsStringAsync(); var xml = await response.Content.ReadAsStringAsync();
if (xml != lastApiXml) lastApiXml = xml;
ParseAndDisplaySongInfo(xml);
try
{ {
lastApiXml = xml; var newKey = $"{marqueeTextArtist}|{marqueeTextTitle}";
ParseAndDisplaySongInfo(xml); if (!string.Equals(newKey, _currentTrackKey, StringComparison.Ordinal))
{
_currentTrackKey = newKey;
ShowTemporaryStatus("Song Change Detected", 2000);
}
} }
catch { }
} }
} }
catch { } catch { }
@@ -279,7 +322,10 @@ namespace RadioDJViewer
private async void button1_Click(object sender, EventArgs e) private async void button1_Click(object sender, EventArgs e)
{ {
// Connect to REST API // Show connecting message
ShowTemporaryStatus("Connecting to API...", 0);
// Connect to REST API (ConnectToRestApi will start web server if needed)
await ConnectToRestApi(); await ConnectToRestApi();
if (isConnected) if (isConnected)
{ {
@@ -292,6 +338,25 @@ namespace RadioDJViewer
// Disconnect // Disconnect
isConnected = false; isConnected = false;
apiTimer.Stop(); apiTimer.Stop();
playbackTimer.Stop();
_playbackElapsedSeconds = 0;
_playbackDurationSeconds = 0;
_playbackPaused = true;
_playbackTrackKey = string.Empty;
time_data_label.Text = "No Data";
WidgetServer.CachedElapsed = "0";
WidgetServer.CachedDuration = "0";
WidgetServer.CachedTime = "No Data";
WidgetServer.CachedPaused = true;
// Stop widget server when disconnecting
try
{
widgetServer?.Stop();
widgetServer = null;
widgetRunning = false;
}
catch { }
ClearStatusMessage();
UpdateStatusBar(); UpdateStatusBar();
} }
@@ -327,7 +392,17 @@ namespace RadioDJViewer
{ {
var xml = await response.Content.ReadAsStringAsync(); var xml = await response.Content.ReadAsStringAsync();
ParseAndDisplaySongInfo(xml); ParseAndDisplaySongInfo(xml);
try
{
_currentTrackKey = $"{marqueeTextArtist}|{marqueeTextTitle}";
}
catch { }
isConnected = true; isConnected = true;
// Clear the 'Connecting...' persistent message and show connected
ClearStatusMessage();
UpdateStatusBar();
// Start web server immediately after API connection if profile requests it
StartWidgetServerIfEnabled();
} }
else else
{ {
@@ -433,6 +508,82 @@ namespace RadioDJViewer
marqueeTimerAlbum.Stop(); marqueeTimerAlbum.Stop();
} }
private void PlaybackTimer_Tick(object sender, EventArgs e)
{
UpdatePlaybackDisplay();
}
private void UpdatePlaybackState(string trackKey, string elapsedText, string durationText)
{
double elapsedSeconds;
double durationSeconds;
if (!TryParseSeconds(elapsedText, out elapsedSeconds) || !TryParseSeconds(durationText, out durationSeconds) || durationSeconds <= 0)
{
playbackTimer.Stop();
_playbackTrackKey = trackKey ?? string.Empty;
_playbackElapsedSeconds = 0;
_playbackDurationSeconds = 0;
_playbackPaused = true;
time_data_label.Text = "No Data";
WidgetServer.CachedElapsed = "0";
WidgetServer.CachedDuration = "0";
WidgetServer.CachedTime = "No Data";
WidgetServer.CachedPaused = true;
return;
}
bool sameTrack = string.Equals(trackKey, _playbackTrackKey, StringComparison.Ordinal);
bool sameElapsed = Math.Abs(elapsedSeconds - _playbackElapsedSeconds) < 0.01;
bool sameDuration = Math.Abs(durationSeconds - _playbackDurationSeconds) < 0.01;
_playbackTrackKey = trackKey ?? string.Empty;
_playbackElapsedSeconds = elapsedSeconds;
_playbackDurationSeconds = durationSeconds;
_playbackBaseUtc = DateTime.UtcNow;
_playbackPaused = sameTrack && sameElapsed && sameDuration;
if (_playbackPaused)
playbackTimer.Stop();
else
playbackTimer.Start();
UpdatePlaybackDisplay();
}
private void UpdatePlaybackDisplay()
{
double currentElapsed = GetCurrentElapsedSeconds();
string timeDisplay = FormatTimeDisplay(currentElapsed, _playbackDurationSeconds);
time_data_label.Text = timeDisplay;
WidgetServer.CachedElapsed = currentElapsed.ToString("0.###", CultureInfo.InvariantCulture);
WidgetServer.CachedDuration = _playbackDurationSeconds.ToString("0.###", CultureInfo.InvariantCulture);
WidgetServer.CachedTime = timeDisplay;
WidgetServer.CachedPaused = _playbackPaused;
}
private double GetCurrentElapsedSeconds()
{
if (_playbackDurationSeconds <= 0)
return 0;
double currentElapsed = _playbackElapsedSeconds;
if (!_playbackPaused)
currentElapsed += (DateTime.UtcNow - _playbackBaseUtc).TotalSeconds;
if (currentElapsed < 0)
currentElapsed = 0;
if (currentElapsed > _playbackDurationSeconds)
currentElapsed = _playbackDurationSeconds;
return currentElapsed;
}
private bool TryParseSeconds(string value, out double seconds)
{
return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out seconds)
|| double.TryParse(value, NumberStyles.Float, CultureInfo.CurrentCulture, out seconds);
}
private void ParseAndDisplaySongInfo(string xml) private void ParseAndDisplaySongInfo(string xml)
{ {
try try
@@ -444,12 +595,17 @@ namespace RadioDJViewer
string artist = doc.SelectSingleNode("//*[translate(local-name(), 'ARTIST', 'artist')='artist']")?.InnerText; string artist = doc.SelectSingleNode("//*[translate(local-name(), 'ARTIST', 'artist')='artist']")?.InnerText;
string album = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUM', 'album')='album']")?.InnerText; string album = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUM', 'album')='album']")?.InnerText;
string albumArt = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUMART', 'albumart')='albumart']")?.InnerText; string albumArt = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUMART', 'albumart')='albumart']")?.InnerText;
// Parse elapsed and duration time (case-insensitive, floating-point seconds)
string elapsed = doc.SelectSingleNode("//*[translate(local-name(), 'ELAPSED', 'elapsed')='elapsed']")?.InnerText;
string duration = doc.SelectSingleNode("//*[translate(local-name(), 'DURATION', 'duration')='duration']")?.InnerText;
marqueeTextTitle = !string.IsNullOrWhiteSpace(title) ? title : "No title"; marqueeTextTitle = !string.IsNullOrWhiteSpace(title) ? title : "No title";
marqueeTextArtist = !string.IsNullOrWhiteSpace(artist) ? artist : "No artist"; marqueeTextArtist = !string.IsNullOrWhiteSpace(artist) ? artist : "No artist";
marqueeTextAlbum = !string.IsNullOrWhiteSpace(album) ? album : "No album"; marqueeTextAlbum = !string.IsNullOrWhiteSpace(album) ? album : "No album";
StartMarqueeTimers(); StartMarqueeTimers();
UpdatePlaybackState($"{marqueeTextArtist}|{marqueeTextTitle}", elapsed, duration);
// Write to output files // Write to output files
if (!string.IsNullOrEmpty(outputFolderPath)) if (!string.IsNullOrEmpty(outputFolderPath))
{ {
@@ -458,6 +614,50 @@ namespace RadioDJViewer
File.WriteAllText(Path.Combine(outputFolderPath, "album.txt"), marqueeTextAlbum); File.WriteAllText(Path.Combine(outputFolderPath, "album.txt"), marqueeTextAlbum);
} }
// Update in-memory cache for widget server
try
{
WidgetServer.CachedArtist = marqueeTextArtist ?? string.Empty;
WidgetServer.CachedTitle = marqueeTextTitle ?? string.Empty;
byte[] imgBytes = null;
// Prefer album art from main images folder
if (!string.IsNullOrEmpty(albumArt) && !string.IsNullOrEmpty(mainImagesFolderPath))
{
var imagePath = Path.Combine(mainImagesFolderPath, albumArt);
if (File.Exists(imagePath))
{
try
{
using (var img = System.Drawing.Image.FromFile(imagePath))
{
using (var ms = new MemoryStream())
{
img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
imgBytes = ms.ToArray();
}
}
}
catch { imgBytes = null; }
}
}
// Fallback to embedded resource image
if (imgBytes == null)
{
var fallback = Properties.Resources.fallback;
if (fallback != null)
{
using (var ms = new MemoryStream())
{
fallback.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
imgBytes = ms.ToArray();
}
}
}
WidgetServer.CachedImageBytes = imgBytes;
}
catch { }
// Handle album art image // Handle album art image
if (!string.IsNullOrWhiteSpace(albumArt)) if (!string.IsNullOrWhiteSpace(albumArt))
{ {
@@ -474,25 +674,120 @@ namespace RadioDJViewer
marqueeTextArtist = "No artist"; marqueeTextArtist = "No artist";
marqueeTextAlbum = "No album"; marqueeTextAlbum = "No album";
StartMarqueeTimers(); StartMarqueeTimers();
playbackTimer.Stop();
_playbackElapsedSeconds = 0;
_playbackDurationSeconds = 0;
_playbackPaused = true;
time_data_label.Text = "No Data";
WidgetServer.CachedElapsed = "0";
WidgetServer.CachedDuration = "0";
WidgetServer.CachedTime = "No Data";
WidgetServer.CachedPaused = true;
pictureBox1.Image = null; pictureBox1.Image = null;
} }
} }
private void UpdateStatusBar() private void UpdateStatusBar()
{ {
// Status icon try
toolStripStatusLabel2.Image = isConnected ? Properties.Resources.green : Properties.Resources.red; {
// Profile if (!isConnected)
{
toolStripStatusLabel2.BackColor = Color.Red;
}
else
{
if (isConnected && widgetRunning)
toolStripStatusLabel2.BackColor = Color.Blue;
else
toolStripStatusLabel2.BackColor = Color.Green;
}
}
catch { }
toolStripStatusLabel4.Text = $"Profile: {currentProfile}"; toolStripStatusLabel4.Text = $"Profile: {currentProfile}";
// Song update notification (example) if (statusCts == null)
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected"; {
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected";
}
}
private void ShowTemporaryStatus(string message, int milliseconds)
{
try
{
statusCts?.Cancel();
}
catch { }
statusCts = null;
if (milliseconds <= 0)
{
var persistent = new CancellationTokenSource();
statusCts = persistent;
toolStripStatusLabel3.Text = message;
return;
}
var cts = new CancellationTokenSource();
statusCts = cts;
toolStripStatusLabel3.Text = message;
Task.Run(async () =>
{
try
{
await Task.Delay(milliseconds, cts.Token);
if (!cts.IsCancellationRequested)
{
statusCts = null;
this.BeginInvoke((Action)(() => {
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected";
}));
}
}
catch { }
});
}
private void ClearStatusMessage()
{
try { statusCts?.Cancel(); } catch { }
statusCts = null;
try { toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected"; } catch { }
}
private void StartWidgetServerIfEnabled()
{
if (loadedProfile == null || !loadedProfile.WebServerEnabled)
return;
try
{
WidgetServer.CachedClockVisibility = loadedProfile.ClockVisibilityEnabled;
if (widgetServer == null)
{
int port = loadedProfile.WebServerPort > 0 ? loadedProfile.WebServerPort : 8080;
widgetServer = new WidgetServer(port, () => marqueeTextArtist, () => marqueeTextTitle, () => this.currentSongImagePath ?? this.defaultImagePath);
}
widgetServer.Start();
widgetRunning = true;
UpdateStatusBar();
ShowTemporaryStatus($"Web: Running on {loadedProfile.WebServerPort}", 5000);
}
catch (Exception ex)
{
widgetRunning = false;
UpdateStatusBar();
ShowTemporaryStatus($"Web server error: {ex.Message}", 5000);
}
} }
private void restAPISettingToolStripMenuItem_Click(object sender, EventArgs e) private void restAPISettingToolStripMenuItem_Click(object sender, EventArgs e)
{ {
// Open settings/profile dialog
var settingsForm = new Form1(); var settingsForm = new Form1();
settingsForm.ApplyTheme(isDarkMode); // Pass current color mode settingsForm.ApplyTheme(isDarkMode);
settingsForm.ShowDialog(); settingsForm.ShowDialog();
} }
@@ -513,25 +808,28 @@ namespace RadioDJViewer
private void toolStripStatusLabel4_Click(object sender, EventArgs e) private void toolStripStatusLabel4_Click(object sender, EventArgs e)
{ {
// Profile click handler (to be implemented)
} }
public void LoadProfile(Profile profile) public void LoadProfile(Profile profile)
{ {
// Update main form fields from profile
currentProfile = profile.Name; currentProfile = profile.Name;
selectedFolderPath = profile.OutputFolder; selectedFolderPath = profile.OutputFolder;
outputFolderPath = profile.OutputFolder; outputFolderPath = profile.OutputFolder;
mainImagesFolderPath = profile.MainImagesFolder; // Update new field mainImagesFolderPath = profile.MainImagesFolder;
loadedProfile = profile; // Store loaded profile for REST API connection loadedProfile = profile;
// Update polling rate when profile is loaded int pollingRate = profile.PollingRateSeconds > 0 ? profile.PollingRateSeconds : 3;
int pollingRate = loadedProfile?.PollingRateSeconds > 0 ? loadedProfile.PollingRateSeconds : 3;
apiTimer.Interval = pollingRate * 1000; apiTimer.Interval = pollingRate * 1000;
SetMarqueeScrollSpeed(profile.MarqueeScrollSpeed ?? "Medium"); SetMarqueeScrollSpeed(profile.MarqueeScrollSpeed ?? "Medium");
SetMarqueePauseTime(profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime : 6); SetMarqueePauseTime(profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime : 6);
SetMarqueeSeparator(profile.MarqueeSeparator ?? " | "); SetMarqueeSeparator(profile.MarqueeSeparator ?? " | ");
if (pauseTimerTitle != null) pauseTimerTitle.Interval = marqueePauseTime;
if (pauseTimerArtist != null) pauseTimerArtist.Interval = marqueePauseTime;
if (pauseTimerAlbum != null) pauseTimerAlbum.Interval = marqueePauseTime;
marqueeTimerTitle.Interval = marqueeScrollSpeed;
marqueeTimerArtist.Interval = marqueeScrollSpeed;
marqueeTimerAlbum.Interval = marqueeScrollSpeed;
WidgetServer.CachedClockVisibility = profile.ClockVisibilityEnabled;
UpdateStatusBar(); UpdateStatusBar();
// You can add more logic to update other fields if needed
} }
private void Uicolor_Click(object sender, EventArgs e) private void Uicolor_Click(object sender, EventArgs e)
@@ -557,23 +855,19 @@ namespace RadioDJViewer
private void UpdateTheme() private void UpdateTheme()
{ {
// Set icon
Bitmap icon = isDarkMode ? Properties.Resources.MdiWeatherSunny : Properties.Resources.MdiWeatherNight; Bitmap icon = isDarkMode ? Properties.Resources.MdiWeatherSunny : Properties.Resources.MdiWeatherNight;
// Invert sun icon for visibility in dark mode
if (isDarkMode && icon != null) if (isDarkMode && icon != null)
{ {
icon = InvertIcon(icon); icon = InvertIcon(icon);
} }
// Resize icon to fit button and center it
if (icon != null) if (icon != null)
{ {
int btnW = uicolor.Width; int btnW = uicolor.Width;
int btnH = uicolor.Height; int btnH = uicolor.Height;
var resizedIcon = new Bitmap(icon, btnW - 8, btnH - 8); // Padding for centering var resizedIcon = new Bitmap(icon, btnW - 8, btnH - 8);
uicolor.Image = resizedIcon; uicolor.Image = resizedIcon;
uicolor.ImageAlign = ContentAlignment.MiddleCenter; uicolor.ImageAlign = ContentAlignment.MiddleCenter;
} }
// Set colors
Color backColor = isDarkMode ? Color.FromArgb(32, 32, 32) : SystemColors.Control; Color backColor = isDarkMode ? Color.FromArgb(32, 32, 32) : SystemColors.Control;
Color foreColor = isDarkMode ? Color.White : SystemColors.ControlText; Color foreColor = isDarkMode ? Color.White : SystemColors.ControlText;
this.BackColor = backColor; this.BackColor = backColor;
@@ -593,5 +887,88 @@ namespace RadioDJViewer
ApplyThemeRecursive(child, backColor, foreColor); ApplyThemeRecursive(child, backColor, foreColor);
} }
} }
private void label7_Click(object sender, EventArgs e)
{
}
private void label9_Click(object sender, EventArgs e)
{
}
public string GetCurrentProfileName()
{
return currentProfile;
}
private void PopulateProfileDropBox()
{
profile_drop_box.Items.Clear();
var profileNames = ProfileStorage.GetProfileNames();
profile_drop_box.Items.AddRange(profileNames.ToArray());
string currentProfile = GetCurrentProfileName();
if (!string.IsNullOrEmpty(currentProfile) && profileNames.Contains(currentProfile))
profile_drop_box.SelectedItem = currentProfile;
else if (profile_drop_box.Items.Count > 0)
profile_drop_box.SelectedIndex = 0;
}
private void load_profile_button_Click(object sender, EventArgs e)
{
if (profile_drop_box.SelectedItem != null)
{
var profile = ProfileStorage.LoadProfile(profile_drop_box.SelectedItem.ToString());
if (profile != null)
{
LoadProfile(profile);
UpdateStatusBar();
MessageBox.Show($"Profile '{profile.Name}' loaded.", "Profile Loaded", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
private string FormatTimeDisplay(string elapsedSeconds, string durationSeconds)
{
double elapsed;
double duration;
if (!TryParseSeconds(elapsedSeconds, out elapsed) || !TryParseSeconds(durationSeconds, out duration))
{
return "No Data";
}
return FormatTimeDisplay(elapsed, duration);
}
private string FormatTimeDisplay(double elapsed, double duration)
{
// If duration is zero or negative, show "No Data"
if (duration <= 0)
{
return "No Data";
}
// Clamp elapsed to [0, duration]
if (elapsed < 0)
elapsed = 0;
if (elapsed > duration)
elapsed = duration;
// Convert to integers (seconds only)
int elapsedInt = (int)elapsed;
int durationInt = (int)duration;
// Convert seconds to MM:SS format
string elapsedFormatted = FormatSeconds(elapsedInt);
string durationFormatted = FormatSeconds(durationInt);
return $"{elapsedFormatted} / {durationFormatted}";
}
private string FormatSeconds(int seconds)
{
int minutes = seconds / 60;
int secs = seconds % 60;
return $"{minutes}:{secs:D2}";
}
} }
} }

View File

@@ -120,17 +120,10 @@
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value> <value>17, 17</value>
</metadata> </metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="toolStripStatusLabel2.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAABGdBTUEAALGPC/xhBQAAAANQTFRFJn8A
Mx1isQAAAAlwSFlzAAAOvAAADrwBlbxySQAAAB9JREFUaN7twTEBAAAAwqD1T20IX6AAAAAAAAAAAD4D
IIAAAT2Y3yoAAAAASUVORK5CYII=
</value>
</data>
<metadata name="menuStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="menuStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>247, 17</value> <value>247, 17</value>
</metadata> </metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
AAABAAEAIBgAAAEAIACIDAAAFgAAACgAAAAgAAAAMAAAAAEAIAAAAAAAAAwAAMMOAADDDgAAAAAAAAAA AAABAAEAIBgAAAEAIACIDAAAFgAAACgAAAAgAAAAMAAAAAEAIAAAAAAAAAwAAMMOAADDDgAAAAAAAAAA

View File

@@ -20,6 +20,11 @@ namespace RadioDJViewer
public string MarqueeScrollSpeed { get; set; } // "Very Slow", etc. public string MarqueeScrollSpeed { get; set; } // "Very Slow", etc.
public int MarqueePauseTime { get; set; } // in seconds public int MarqueePauseTime { get; set; } // in seconds
public string MarqueeSeparator { get; set; } // e.g. " | " public string MarqueeSeparator { get; set; } // e.g. " | "
// Web server settings
public bool WebServerEnabled { get; set; }
public int WebServerPort { get; set; }
// Clock visibility setting
public bool ClockVisibilityEnabled { get; set; }
} }
public static class ProfileStorage public static class ProfileStorage

View File

@@ -6,9 +6,9 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
// associated with an assembly. // associated with an assembly.
[assembly: AssemblyTitle("RadioDJViewer")] [assembly: AssemblyTitle("RadioDJViewer")]
[assembly: AssemblyDescription("")] [assembly: AssemblyDescription("RadioDJ Metadata OBS Extractor")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("Smartcraft Media Group")]
[assembly: AssemblyProduct("RadioDJViewer")] [assembly: AssemblyProduct("RadioDJViewer")]
[assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
// Build Number // Build Number
// Revision // Revision
// //
[assembly: AssemblyVersion("1.1.0")] [assembly: AssemblyVersion("1.10.0")]
[assembly: AssemblyFileVersion("1.1.0")] [assembly: AssemblyFileVersion("1.10.0")]

View File

@@ -13,6 +13,21 @@
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<ApplicationIcon>icon.ico</ApplicationIcon> <ApplicationIcon>icon.ico</ApplicationIcon>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@@ -53,6 +68,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="EmbeddedResourceHelper.cs" />
<Compile Include="ProfileStorage.cs" /> <Compile Include="ProfileStorage.cs" />
<Compile Include="radiodj-restapi-template.cs"> <Compile Include="radiodj-restapi-template.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
@@ -68,6 +84,7 @@
</Compile> </Compile>
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WidgetServer.cs" />
<EmbeddedResource Include="radiodj-restapi-template.resx"> <EmbeddedResource Include="radiodj-restapi-template.resx">
<DependentUpon>radiodj-restapi-template.cs</DependentUpon> <DependentUpon>radiodj-restapi-template.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
@@ -109,8 +126,23 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="icon.ico" /> <Content Include="icon.ico" />
<EmbeddedResource Include="index.html" />
<EmbeddedResource Include="script.js" />
<EmbeddedResource Include="style.css" />
<None Include="Resources\MdiWeatherSunny.png" /> <None Include="Resources\MdiWeatherSunny.png" />
<None Include="Resources\MdiWeatherNight.png" /> <None Include="Resources\MdiWeatherNight.png" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View File

@@ -0,0 +1,302 @@
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace RadioDJViewer
{
/// <summary>
/// Simple embedded-resource-backed HTTP widget server.
/// Serves:
/// - / or /index.html -> embedded index.html with {{ARTIST}} and {{TITLE}} replaced
/// - /style.css -> embedded style.css
/// - /script.js -> embedded script.js
/// - /albumart -> the current album art image bytes (uses provided delegate to locate file)
/// Runs on a background Task and can be stopped via Stop().
/// </summary>
public class WidgetServer
{
// In-memory cache for fast serving
public static string CachedArtist = "";
public static string CachedTitle = "";
public static string CachedTime = "No Data";
public static string CachedElapsed = "0";
public static string CachedDuration = "0";
public static bool CachedPaused = true;
public static byte[] CachedImageBytes = null;
public static bool CachedClockVisibility = false;
private readonly int port;
private readonly Func<string> getArtist;
private readonly Func<string> getTitle;
private readonly Func<string> getAlbumArtPath;
private readonly HttpListener listener;
private CancellationTokenSource cts;
private readonly string htmlTemplate;
private readonly string cssText;
private readonly string jsText;
public WidgetServer(int port, Func<string> getArtist, Func<string> getTitle, Func<string> getAlbumArtPath)
{
this.port = port;
this.getArtist = getArtist ?? (() => string.Empty);
this.getTitle = getTitle ?? (() => string.Empty);
this.getAlbumArtPath = getAlbumArtPath ?? (() => string.Empty);
listener = new HttpListener();
listener.Prefixes.Add($"http://+:{port}/");
// Preload embedded resources
htmlTemplate = EmbeddedResourceHelper.Read("index.html");
cssText = EmbeddedResourceHelper.Read("style.css");
jsText = EmbeddedResourceHelper.Read("script.js");
}
public void Start()
{
if (cts != null) return; // already started
cts = new CancellationTokenSource();
listener.Start();
Task.Run(() => RunAsync(cts.Token));
}
public void Stop()
{
try
{
cts?.Cancel();
listener.Stop();
}
catch { }
finally
{
cts = null;
}
}
private async Task RunAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
HttpListenerContext ctx = null;
try
{
ctx = await listener.GetContextAsync().ConfigureAwait(false);
}
catch (HttpListenerException)
{
// listener closed
break;
}
catch (ObjectDisposedException)
{
break;
}
catch (Exception)
{
continue;
}
_ = Task.Run(() => HandleContext(ctx), token);
}
}
private void HandleContext(HttpListenerContext ctx)
{
try
{
var req = ctx.Request;
var resp = ctx.Response;
string path = req.Url.AbsolutePath.TrimEnd('/');
if (string.IsNullOrEmpty(path) || path == "") path = "/";
if (path == "/" || path.Equals("/index.html", StringComparison.OrdinalIgnoreCase))
{
var html = htmlTemplate ?? "";
// Replace placeholders with in-memory cache values
html = html.Replace("{{ARTIST}}", WebUtility.HtmlEncode(CachedArtist ?? string.Empty))
.Replace("{{TITLE}}", WebUtility.HtmlEncode(CachedTitle ?? string.Empty))
.Replace("{{TIME}}", WebUtility.HtmlEncode(CachedTime ?? string.Empty));
var buf = Encoding.UTF8.GetBytes(html);
resp.ContentType = "text/html; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/status.json", StringComparison.OrdinalIgnoreCase))
{
var json = "{"
+ $"\"artist\":\"{JsonEscape(CachedArtist ?? string.Empty)}\","
+ $"\"title\":\"{JsonEscape(CachedTitle ?? string.Empty)}\","
+ $"\"time\":\"{JsonEscape(CachedTime ?? "No Data")}\","
+ $"\"elapsed\":\"{JsonEscape(CachedElapsed ?? "0")}\","
+ $"\"duration\":\"{JsonEscape(CachedDuration ?? "0")}\","
+ $"\"paused\":{(CachedPaused ? "true" : "false")},"
+ $"\"clockVisible\":{(CachedClockVisibility ? "true" : "false")}"
+ "}";
var buf = Encoding.UTF8.GetBytes(json);
resp.ContentType = "application/json; charset=utf-8";
resp.Headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0";
resp.Headers["Pragma"] = "no-cache";
resp.Headers["Expires"] = "0";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/artist.txt", StringComparison.OrdinalIgnoreCase))
{
ApplyNoCache(resp);
var buf = Encoding.UTF8.GetBytes(CachedArtist ?? string.Empty);
resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/title.txt", StringComparison.OrdinalIgnoreCase))
{
ApplyNoCache(resp);
var buf = Encoding.UTF8.GetBytes(CachedTitle ?? string.Empty);
resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/time.txt", StringComparison.OrdinalIgnoreCase))
{
ApplyNoCache(resp);
var buf = Encoding.UTF8.GetBytes(CachedTime ?? "No Data");
resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/elapsed", StringComparison.OrdinalIgnoreCase))
{
ApplyNoCache(resp);
var buf = Encoding.UTF8.GetBytes(CachedElapsed ?? "0");
resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/duration", StringComparison.OrdinalIgnoreCase))
{
ApplyNoCache(resp);
var buf = Encoding.UTF8.GetBytes(CachedDuration ?? "0");
resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/clock-visibility", StringComparison.OrdinalIgnoreCase))
{
ApplyNoCache(resp);
var buf = Encoding.UTF8.GetBytes(CachedClockVisibility ? "true" : "false");
resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/style.css", StringComparison.OrdinalIgnoreCase))
{
var buf = Encoding.UTF8.GetBytes(cssText ?? "");
resp.ContentType = "text/css; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/script.js", StringComparison.OrdinalIgnoreCase))
{
var buf = Encoding.UTF8.GetBytes(jsText ?? "");
resp.ContentType = "application/javascript; charset=utf-8";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
else if (path.Equals("/Album-Art.png", StringComparison.OrdinalIgnoreCase) || path.Equals("/album-art.png", StringComparison.OrdinalIgnoreCase))
{
if (CachedImageBytes != null && CachedImageBytes.Length > 0)
{
try
{
resp.ContentType = "image/png";
resp.ContentLength64 = CachedImageBytes.Length;
resp.OutputStream.Write(CachedImageBytes, 0, CachedImageBytes.Length);
}
catch
{
ServeFallbackImage(resp);
}
}
else
{
ServeFallbackImage(resp);
}
}
else
{
resp.StatusCode = 404;
var msg = Encoding.UTF8.GetBytes("Not found");
resp.OutputStream.Write(msg, 0, msg.Length);
}
resp.OutputStream.Close();
}
catch
{
try { ctx.Response.OutputStream.Close(); } catch { }
}
}
private void ServeFallbackImage(HttpListenerResponse resp)
{
try
{
var img = Properties.Resources.fallback;
if (img != null)
{
using (var ms = new MemoryStream())
{
img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
var buf = ms.ToArray();
resp.ContentType = "image/png";
resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, buf.Length);
}
}
else
{
resp.StatusCode = 404;
var msg = Encoding.UTF8.GetBytes("No image");
resp.OutputStream.Write(msg, 0, msg.Length);
}
}
catch
{
resp.StatusCode = 500;
}
}
private static void ApplyNoCache(HttpListenerResponse resp)
{
resp.Headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0";
resp.Headers["Pragma"] = "no-cache";
resp.Headers["Expires"] = "0";
}
private static string JsonEscape(string value)
{
if (string.IsNullOrEmpty(value))
return string.Empty;
return value.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\r", "\\r")
.Replace("\n", "\\n");
}
private string GetMimeTypeFromPath(string path)
{
var ext = Path.GetExtension(path).ToLowerInvariant();
switch (ext)
{
case ".png": return "image/png";
case ".jpg":
case ".jpeg": return "image/jpeg";
case ".gif": return "image/gif";
default: return "application/octet-stream";
}
}
}
}

37
RadioDJViewer/index.html Normal file
View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Radio DJ Now Playing</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="widget-container">
<!-- Blurred background layer -->
<div class="background-blur">
<img class="background-image" src="Album-Art.png" alt="Album backdrop">
</div>
<!-- Dark overlay for contrast -->
<div class="overlay"></div>
<!-- Content layer -->
<div class="widget-content">
<!-- Album art (left) -->
<div class="album-art-box">
<img class="album-art" src="Album-Art.png" alt="Album art">
</div>
<!-- Song info (right) -->
<div class="song-info">
<div class="song-title">{{TITLE}}</div>
<div class="song-artist">{{ARTIST}}</div>
<div class="song-time" id="song-time">{{TIME}}</div>
</div>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>

View File

@@ -32,7 +32,7 @@
this.groupBox1 = new System.Windows.Forms.GroupBox(); this.groupBox1 = new System.Windows.Forms.GroupBox();
this.textBox1 = new System.Windows.Forms.TextBox(); this.textBox1 = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label(); this.label1 = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button(); this.ok_button_master = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button(); this.button2 = new System.Windows.Forms.Button();
this.comboBox1 = new System.Windows.Forms.ComboBox(); this.comboBox1 = new System.Windows.Forms.ComboBox();
this.label2 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label();
@@ -51,12 +51,17 @@
this.label10 = new System.Windows.Forms.Label(); this.label10 = new System.Windows.Forms.Label();
this.label11 = new System.Windows.Forms.Label(); this.label11 = new System.Windows.Forms.Label();
this.textBox6 = new System.Windows.Forms.TextBox(); this.textBox6 = new System.Windows.Forms.TextBox();
this.button5 = new System.Windows.Forms.Button();
this.button6 = new System.Windows.Forms.Button(); this.button6 = new System.Windows.Forms.Button();
this.label6 = new System.Windows.Forms.Label(); this.label6 = new System.Windows.Forms.Label();
this.label7 = new System.Windows.Forms.Label(); this.label7 = new System.Windows.Forms.Label();
this.textBox7 = new System.Windows.Forms.TextBox(); this.textBox7 = new System.Windows.Forms.TextBox();
this.groupBox2 = new System.Windows.Forms.GroupBox(); this.groupBox2 = new System.Windows.Forms.GroupBox();
this.web_server_settings_groupbox = new System.Windows.Forms.GroupBox();
this.web_server_port_textbox = new System.Windows.Forms.TextBox();
this.web_server_port_label = new System.Windows.Forms.Label();
this.web_server_label = new System.Windows.Forms.Label();
this.web_server_checkbox = new System.Windows.Forms.CheckBox();
this.label13 = new System.Windows.Forms.Label();
this.comboBoxScrollSpeed = new System.Windows.Forms.ComboBox(); this.comboBoxScrollSpeed = new System.Windows.Forms.ComboBox();
this.textBoxPauseTime = new System.Windows.Forms.TextBox(); this.textBoxPauseTime = new System.Windows.Forms.TextBox();
this.separatingcharacterformatbox = new System.Windows.Forms.TextBox(); this.separatingcharacterformatbox = new System.Windows.Forms.TextBox();
@@ -64,10 +69,12 @@
this.label9 = new System.Windows.Forms.Label(); this.label9 = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label(); this.label8 = new System.Windows.Forms.Label();
this.label12 = new System.Windows.Forms.Label(); this.label12 = new System.Windows.Forms.Label();
this.label13 = new System.Windows.Forms.Label(); this.clock_label = new System.Windows.Forms.Label();
this.clock_visibility_checkBox = new System.Windows.Forms.CheckBox();
this.groupBox1.SuspendLayout(); this.groupBox1.SuspendLayout();
this.groupBox3.SuspendLayout(); this.groupBox3.SuspendLayout();
this.groupBox2.SuspendLayout(); this.groupBox2.SuspendLayout();
this.web_server_settings_groupbox.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// groupBox1 // groupBox1
@@ -97,18 +104,18 @@
this.label1.TabIndex = 0; this.label1.TabIndex = 0;
this.label1.Text = "URL Format:"; this.label1.Text = "URL Format:";
// //
// button1 // ok_button_master
// //
this.button1.Location = new System.Drawing.Point(35, 584); this.ok_button_master.Location = new System.Drawing.Point(65, 694);
this.button1.Name = "button1"; this.ok_button_master.Name = "ok_button_master";
this.button1.Size = new System.Drawing.Size(157, 23); this.ok_button_master.Size = new System.Drawing.Size(157, 23);
this.button1.TabIndex = 1; this.ok_button_master.TabIndex = 1;
this.button1.Text = "Save"; this.ok_button_master.Text = "OK";
this.button1.UseVisualStyleBackColor = true; this.ok_button_master.UseVisualStyleBackColor = true;
// //
// button2 // button2
// //
this.button2.Location = new System.Drawing.Point(357, 584); this.button2.Location = new System.Drawing.Point(305, 694);
this.button2.Name = "button2"; this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(157, 23); this.button2.Size = new System.Drawing.Size(157, 23);
this.button2.TabIndex = 2; this.button2.TabIndex = 2;
@@ -182,7 +189,7 @@
// //
// button3 // button3
// //
this.button3.Location = new System.Drawing.Point(60, 419); this.button3.Location = new System.Drawing.Point(71, 535);
this.button3.Name = "button3"; this.button3.Name = "button3";
this.button3.Size = new System.Drawing.Size(120, 30); this.button3.Size = new System.Drawing.Size(120, 30);
this.button3.TabIndex = 8; this.button3.TabIndex = 8;
@@ -191,7 +198,7 @@
// //
// button4 // button4
// //
this.button4.Location = new System.Drawing.Point(216, 419); this.button4.Location = new System.Drawing.Point(316, 535);
this.button4.Name = "button4"; this.button4.Name = "button4";
this.button4.Size = new System.Drawing.Size(120, 30); this.button4.Size = new System.Drawing.Size(120, 30);
this.button4.TabIndex = 9; this.button4.TabIndex = 9;
@@ -204,7 +211,7 @@
this.groupBox3.Controls.Add(this.labelOutputFolderPath); this.groupBox3.Controls.Add(this.labelOutputFolderPath);
this.groupBox3.Controls.Add(this.textBox5); this.groupBox3.Controls.Add(this.textBox5);
this.groupBox3.Controls.Add(this.label10); this.groupBox3.Controls.Add(this.label10);
this.groupBox3.Location = new System.Drawing.Point(20, 298); this.groupBox3.Location = new System.Drawing.Point(22, 413);
this.groupBox3.Name = "groupBox3"; this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(482, 115); this.groupBox3.Size = new System.Drawing.Size(482, 115);
this.groupBox3.TabIndex = 12; this.groupBox3.TabIndex = 12;
@@ -261,15 +268,6 @@
this.textBox6.Size = new System.Drawing.Size(290, 20); this.textBox6.Size = new System.Drawing.Size(290, 20);
this.textBox6.TabIndex = 15; this.textBox6.TabIndex = 15;
// //
// button5
//
this.button5.Location = new System.Drawing.Point(382, 419);
this.button5.Name = "button5";
this.button5.Size = new System.Drawing.Size(120, 30);
this.button5.TabIndex = 16;
this.button5.Text = "Load Profile";
this.button5.UseVisualStyleBackColor = true;
//
// button6 // button6
// //
this.button6.Location = new System.Drawing.Point(23, 269); this.button6.Location = new System.Drawing.Point(23, 269);
@@ -306,6 +304,7 @@
// //
// groupBox2 // groupBox2
// //
this.groupBox2.Controls.Add(this.web_server_settings_groupbox);
this.groupBox2.Controls.Add(this.label13); this.groupBox2.Controls.Add(this.label13);
this.groupBox2.Controls.Add(this.comboBoxScrollSpeed); this.groupBox2.Controls.Add(this.comboBoxScrollSpeed);
this.groupBox2.Controls.Add(this.textBoxPauseTime); this.groupBox2.Controls.Add(this.textBoxPauseTime);
@@ -319,7 +318,6 @@
this.groupBox2.Controls.Add(this.label7); this.groupBox2.Controls.Add(this.label7);
this.groupBox2.Controls.Add(this.label6); this.groupBox2.Controls.Add(this.label6);
this.groupBox2.Controls.Add(this.button6); this.groupBox2.Controls.Add(this.button6);
this.groupBox2.Controls.Add(this.button5);
this.groupBox2.Controls.Add(this.groupBox3); this.groupBox2.Controls.Add(this.groupBox3);
this.groupBox2.Controls.Add(this.button4); this.groupBox2.Controls.Add(this.button4);
this.groupBox2.Controls.Add(this.button3); this.groupBox2.Controls.Add(this.button3);
@@ -333,11 +331,70 @@
this.groupBox2.Controls.Add(this.comboBox1); this.groupBox2.Controls.Add(this.comboBox1);
this.groupBox2.Location = new System.Drawing.Point(12, 100); this.groupBox2.Location = new System.Drawing.Point(12, 100);
this.groupBox2.Name = "groupBox2"; this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(519, 470); this.groupBox2.Size = new System.Drawing.Size(519, 575);
this.groupBox2.TabIndex = 3; this.groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false; this.groupBox2.TabStop = false;
this.groupBox2.Text = "Profiles"; this.groupBox2.Text = "Profiles";
// //
// web_server_settings_groupbox
//
this.web_server_settings_groupbox.Controls.Add(this.clock_visibility_checkBox);
this.web_server_settings_groupbox.Controls.Add(this.clock_label);
this.web_server_settings_groupbox.Controls.Add(this.web_server_port_textbox);
this.web_server_settings_groupbox.Controls.Add(this.web_server_port_label);
this.web_server_settings_groupbox.Controls.Add(this.web_server_label);
this.web_server_settings_groupbox.Controls.Add(this.web_server_checkbox);
this.web_server_settings_groupbox.Location = new System.Drawing.Point(31, 308);
this.web_server_settings_groupbox.Name = "web_server_settings_groupbox";
this.web_server_settings_groupbox.Size = new System.Drawing.Size(471, 99);
this.web_server_settings_groupbox.TabIndex = 35;
this.web_server_settings_groupbox.TabStop = false;
this.web_server_settings_groupbox.Text = "Web Server Settings";
//
// web_server_port_textbox
//
this.web_server_port_textbox.Location = new System.Drawing.Point(246, 19);
this.web_server_port_textbox.Name = "web_server_port_textbox";
this.web_server_port_textbox.Size = new System.Drawing.Size(77, 20);
this.web_server_port_textbox.TabIndex = 36;
//
// web_server_port_label
//
this.web_server_port_label.AutoSize = true;
this.web_server_port_label.Location = new System.Drawing.Point(211, 23);
this.web_server_port_label.Name = "web_server_port_label";
this.web_server_port_label.Size = new System.Drawing.Size(29, 13);
this.web_server_port_label.TabIndex = 35;
this.web_server_port_label.Text = "Port:";
//
// web_server_label
//
this.web_server_label.AutoSize = true;
this.web_server_label.Location = new System.Drawing.Point(44, 24);
this.web_server_label.Name = "web_server_label";
this.web_server_label.Size = new System.Drawing.Size(67, 13);
this.web_server_label.TabIndex = 33;
this.web_server_label.Text = "Web Server:";
//
// web_server_checkbox
//
this.web_server_checkbox.AutoSize = true;
this.web_server_checkbox.Location = new System.Drawing.Point(111, 23);
this.web_server_checkbox.Name = "web_server_checkbox";
this.web_server_checkbox.Size = new System.Drawing.Size(65, 17);
this.web_server_checkbox.TabIndex = 34;
this.web_server_checkbox.Text = "Enabled";
this.web_server_checkbox.UseVisualStyleBackColor = true;
//
// label13
//
this.label13.AutoSize = true;
this.label13.Location = new System.Drawing.Point(20, 240);
this.label13.Name = "label13";
this.label13.Size = new System.Drawing.Size(142, 13);
this.label13.TabIndex = 32;
this.label13.Text = "Separating Character Format";
//
// comboBoxScrollSpeed // comboBoxScrollSpeed
// //
this.comboBoxScrollSpeed.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.comboBoxScrollSpeed.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
@@ -402,23 +459,33 @@
this.label12.TabIndex = 27; this.label12.TabIndex = 27;
this.label12.Text = "Separating character format"; this.label12.Text = "Separating character format";
// //
// label13 // clock_label
// //
this.label13.AutoSize = true; this.clock_label.AutoSize = true;
this.label13.Location = new System.Drawing.Point(20, 240); this.clock_label.Location = new System.Drawing.Point(44, 56);
this.label13.Name = "label13"; this.clock_label.Name = "clock_label";
this.label13.Size = new System.Drawing.Size(142, 13); this.clock_label.Size = new System.Drawing.Size(76, 13);
this.label13.TabIndex = 32; this.clock_label.TabIndex = 37;
this.label13.Text = "Separating Character Format"; this.clock_label.Text = "Clock Visibility:";
//
// clock_visibility_checkBox
//
this.clock_visibility_checkBox.AutoSize = true;
this.clock_visibility_checkBox.Location = new System.Drawing.Point(119, 55);
this.clock_visibility_checkBox.Name = "clock_visibility_checkBox";
this.clock_visibility_checkBox.Size = new System.Drawing.Size(65, 17);
this.clock_visibility_checkBox.TabIndex = 38;
this.clock_visibility_checkBox.Text = "Enabled";
this.clock_visibility_checkBox.UseVisualStyleBackColor = true;
// //
// Form1 // Form1
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(543, 619); this.ClientSize = new System.Drawing.Size(572, 729);
this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox2);
this.Controls.Add(this.button2); this.Controls.Add(this.button2);
this.Controls.Add(this.button1); this.Controls.Add(this.ok_button_master);
this.Controls.Add(this.groupBox1); this.Controls.Add(this.groupBox1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "Form1"; this.Name = "Form1";
@@ -429,6 +496,8 @@
this.groupBox3.PerformLayout(); this.groupBox3.PerformLayout();
this.groupBox2.ResumeLayout(false); this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout(); this.groupBox2.PerformLayout();
this.web_server_settings_groupbox.ResumeLayout(false);
this.web_server_settings_groupbox.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@@ -438,7 +507,7 @@
private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.TextBox textBox1; private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button button1; private System.Windows.Forms.Button ok_button_master;
private System.Windows.Forms.Button button2; private System.Windows.Forms.Button button2;
private System.Windows.Forms.ComboBox comboBox1; private System.Windows.Forms.ComboBox comboBox1;
private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label2;
@@ -457,7 +526,6 @@
private System.Windows.Forms.Label label10; private System.Windows.Forms.Label label10;
private System.Windows.Forms.Label label11; private System.Windows.Forms.Label label11;
private System.Windows.Forms.TextBox textBox6; private System.Windows.Forms.TextBox textBox6;
private System.Windows.Forms.Button button5;
private System.Windows.Forms.Button button6; private System.Windows.Forms.Button button6;
private System.Windows.Forms.Label label6; private System.Windows.Forms.Label label6;
private System.Windows.Forms.Label label7; private System.Windows.Forms.Label label7;
@@ -473,5 +541,12 @@
private System.Windows.Forms.TextBox textBoxPauseTime; private System.Windows.Forms.TextBox textBoxPauseTime;
private System.Windows.Forms.TextBox separatingcharacterformatbox; private System.Windows.Forms.TextBox separatingcharacterformatbox;
private System.Windows.Forms.Label label13; private System.Windows.Forms.Label label13;
private System.Windows.Forms.GroupBox web_server_settings_groupbox;
private System.Windows.Forms.Label web_server_label;
private System.Windows.Forms.CheckBox web_server_checkbox;
private System.Windows.Forms.TextBox web_server_port_textbox;
private System.Windows.Forms.Label web_server_port_label;
private System.Windows.Forms.CheckBox clock_visibility_checkBox;
private System.Windows.Forms.Label clock_label;
} }
} }

View File

@@ -32,9 +32,8 @@ namespace RadioDJViewer
this.buttonSelectOutputFolder.Click += ButtonSelectOutputFolder_Click; this.buttonSelectOutputFolder.Click += ButtonSelectOutputFolder_Click;
this.button6.Click += ButtonSelectMainImagesFolder_Click; this.button6.Click += ButtonSelectMainImagesFolder_Click;
this.button3.Click += button3_Click; this.button3.Click += button3_Click;
this.button1.Click += button1_Click; this.ok_button_master.Click += button1_Click;
this.button4.Click += button4_Click; this.button4.Click += button4_Click;
this.button5.Click += button5_Click;
this.button2.Click += button2_Click; this.button2.Click += button2_Click;
// Populate profile list on open // Populate profile list on open
LoadProfileList(); LoadProfileList();
@@ -79,6 +78,15 @@ namespace RadioDJViewer
comboBoxScrollSpeed.SelectedItem = profile.MarqueeScrollSpeed ?? "Medium"; comboBoxScrollSpeed.SelectedItem = profile.MarqueeScrollSpeed ?? "Medium";
textBoxPauseTime.Text = profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime.ToString() : "6"; textBoxPauseTime.Text = profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime.ToString() : "6";
separatingcharacterformatbox.Text = profile.MarqueeSeparator ?? " | "; separatingcharacterformatbox.Text = profile.MarqueeSeparator ?? " | ";
// Web server settings
try
{
web_server_checkbox.Checked = profile.WebServerEnabled;
web_server_port_textbox.Text = (profile.WebServerPort > 0 ? profile.WebServerPort : 8080).ToString();
// Clock visibility setting
clock_visibility_checkBox.Checked = profile.ClockVisibilityEnabled;
}
catch { }
} }
} }
} }
@@ -130,7 +138,12 @@ namespace RadioDJViewer
PollingRateSeconds = pollingRate, PollingRateSeconds = pollingRate,
MarqueeScrollSpeed = comboBoxScrollSpeed.SelectedItem?.ToString() ?? "Medium", MarqueeScrollSpeed = comboBoxScrollSpeed.SelectedItem?.ToString() ?? "Medium",
MarqueePauseTime = int.TryParse(textBoxPauseTime.Text, out int pt) ? pt : 6, MarqueePauseTime = int.TryParse(textBoxPauseTime.Text, out int pt) ? pt : 6,
MarqueeSeparator = separatingcharacterformatbox.Text MarqueeSeparator = separatingcharacterformatbox.Text,
// Web server settings
WebServerEnabled = web_server_checkbox.Checked,
WebServerPort = int.TryParse(web_server_port_textbox.Text, out int wp) ? wp : 8080,
// Clock visibility setting
ClockVisibilityEnabled = clock_visibility_checkBox.Checked
}; };
ProfileStorage.SaveProfile(profile); ProfileStorage.SaveProfile(profile);
// Refresh profile list // Refresh profile list
@@ -152,33 +165,6 @@ namespace RadioDJViewer
} }
} }
private void button5_Click(object sender, EventArgs e)
{
// Load Profile (Load Profile button)
if (comboBox1.SelectedItem != null)
{
var profile = ProfileStorage.LoadProfile(comboBox1.SelectedItem.ToString());
if (profile != null)
{
textBox6.Text = profile.Name;
textBox2.Text = profile.IP;
textBox3.Text = profile.Port;
textBox4.Text = profile.Password;
outputFolderPath = profile.OutputFolder;
mainImagesFolderPath = profile.MainImagesFolder;
labelOutputFolderPath.Text = outputFolderPath;
label6.Text = mainImagesFolderPath;
textBox5.Text = profile.OutputImageName;
textBox1.Text = profile.UrlFormat;
Main mainForm = Application.OpenForms["Main"] as Main;
if (mainForm != null)
{
mainForm.LoadProfile(profile);
}
}
}
}
private void button1_Click(object sender, EventArgs e) private void button1_Click(object sender, EventArgs e)
{ {
// Save (bottom Save button) - save all fields to the currently selected or new profile // Save (bottom Save button) - save all fields to the currently selected or new profile

171
RadioDJViewer/script.js Normal file
View File

@@ -0,0 +1,171 @@
(function () {
var IMAGE_FILENAME = "Album-Art.png";
var DEFAULT_REFRESH_RATE_MS = 1000;
var DEFAULT_TIME_REFRESH_RATE_MS = 100;
var currentArtist = getTextContent(".song-artist");
var currentTitle = getTextContent(".song-title");
var currentTime = getTextContent(".song-time");
var refreshInterval = null;
var timeRefreshInterval = null;
var latestTimeRequestId = 0;
var refreshRateMs = getRefreshRateMs();
var timeRefreshRateMs = DEFAULT_TIME_REFRESH_RATE_MS;
function getTextContent(selector) {
var element = document.querySelector(selector);
if (!element || typeof element.textContent !== "string") {
return "";
}
return trimText(element.textContent);
}
function trimText(value) {
return String(value || "").replace(/^\s+|\s+$/g, "");
}
function getRefreshRateMs() {
var match = /(?:\?|&)refreshrate=([^&]+)/i.exec(window.location.search || "");
var seconds = match ? parseFloat(decodeURIComponent(match[1])) : 1;
if (!isFinite(seconds) || seconds <= 0) {
seconds = 1;
}
var interval = Math.round(seconds * 1000);
if (interval < 250) {
interval = 250;
}
return interval;
}
function fetchText(path, fallbackValue, callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", path + "?v=" + new Date().getTime(), true);
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) {
return;
}
if (xhr.status >= 200 && xhr.status < 300) {
callback(trimText(xhr.responseText));
return;
}
callback(fallbackValue);
};
xhr.onerror = function () {
callback(fallbackValue);
};
try {
xhr.send(null);
} catch (e) {
callback(fallbackValue);
}
}
function updateSongDisplay(data) {
var titleElement = document.querySelector(".song-title");
var artistElement = document.querySelector(".song-artist");
var trackChanged = false;
var nextTitle;
var nextArtist;
if (!titleElement || !artistElement || !data) {
return false;
}
nextTitle = trimText(data.title || "");
nextArtist = trimText(data.artist || "");
if (nextTitle !== currentTitle) {
currentTitle = nextTitle;
titleElement.classList.add("updating");
titleElement.textContent = currentTitle || "Unknown Title";
trackChanged = true;
setTimeout(function () {
titleElement.classList.remove("updating");
}, 400);
}
if (nextArtist !== currentArtist) {
currentArtist = nextArtist;
artistElement.classList.add("updating");
artistElement.textContent = currentArtist || "Unknown Artist";
trackChanged = true;
setTimeout(function () {
artistElement.classList.remove("updating");
}, 400);
}
return trackChanged;
}
function updateTimeDisplay(nextTime) {
var timeElement = document.querySelector(".song-time");
var normalizedTime = trimText(nextTime || "No Data") || "No Data";
if (!timeElement) {
return;
}
if (normalizedTime !== currentTime) {
currentTime = normalizedTime;
timeElement.textContent = currentTime;
}
}
function updateAlbumArt() {
var albumArt = document.querySelector(".album-art");
var backgroundImage = document.querySelector(".background-image");
var imageUrl = IMAGE_FILENAME + "?v=" + new Date().getTime();
if (!albumArt || !backgroundImage) {
return;
}
albumArt.src = imageUrl;
backgroundImage.src = imageUrl;
}
function setClockVisibility(visible) {
var timeElement = document.querySelector(".song-time");
if (!timeElement) {
return;
}
timeElement.style.display = visible ? "" : "none";
}
function refreshMetadata() {
fetchText("artist.txt", currentArtist || "", function (artist) {
fetchText("title.txt", currentTitle || "", function (title) {
fetchText("clock-visibility", "true", function (clockVisible) {
var trackChanged = updateSongDisplay({
artist: artist,
title: title
});
setClockVisibility(String(clockVisible || "false").toLowerCase() === "true");
if (trackChanged) {
updateAlbumArt();
}
});
});
});
}
function refreshTime() {
var requestId = latestTimeRequestId + 1;
latestTimeRequestId = requestId;
fetchText("time.txt", currentTime || "No Data", function (nextTime) {
if (requestId !== latestTimeRequestId) {
return;
}
updateTimeDisplay(nextTime);
});
}
function init() {
refreshMetadata();
refreshTime();
refreshInterval = window.setInterval(refreshMetadata, refreshRateMs);
timeRefreshInterval = window.setInterval(refreshTime, timeRefreshRateMs);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

177
RadioDJViewer/style.css Normal file
View File

@@ -0,0 +1,177 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
}
/* Main widget container */
.widget-container {
position: relative;
display: flex;
width: 100%;
max-width: 650px;
aspect-ratio: auto;
height: 200px;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.95);
margin: 20px;
}
/* Blurred background layer (z-index: 0) */
.background-blur {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
overflow: hidden;
}
.background-image {
width: 100%;
height: 100%;
object-fit: cover;
filter: blur(40px) brightness(0.6);
}
/* Dark overlay (z-index: 1) */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
pointer-events: none;
}
/* Content layer (z-index: 2) */
.widget-content {
position: relative;
display: flex;
gap: 24px;
padding: 20px;
z-index: 2;
width: 100%;
height: 100%;
align-items: center;
}
/* Album art box (left) */
.album-art-box {
flex-shrink: 0;
width: 140px;
height: 140px;
border-radius: 10px;
overflow: hidden;
background: rgba(60, 60, 60, 0.5);
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.6);
}
.album-art {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Song info box (right) */
.song-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
gap: 4px;
min-width: 0;
padding: 0 12px;
}
/* Song title */
.song-title {
font-size: 30px;
font-weight: 700;
color: #ffffff;
text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.9);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: -0.5px;
line-height: 1.1;
}
/* Song artist */
.song-artist {
font-size: 22px;
font-weight: 500;
color: #ffffff;
text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.9);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: -0.3px;
line-height: 1.1;
opacity: 1;
}
/* Song time display (underneath artist) */
.song-time {
font-size: 14px;
font-weight: 400;
color: #cccccc;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: 0px;
line-height: 1.1;
opacity: 0.9;
margin-top: 4px;
}
/* Smooth fade animation for updates */
@keyframes fadeIn {
from {
opacity: 0.5;
}
to {
opacity: 1;
}
}
.song-title.updating,
.song-artist.updating {
animation: fadeIn 0.4s ease-in-out;
}
/* Clock section - positioned at bottom of page */
.clock-section {
width: 100%;
margin-top: 20px;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.digital-clock {
font-size: 48px;
font-weight: 300;
color: #ffffff;
text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.9);
font-family: 'Courier New', monospace;
letter-spacing: 2px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB