Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b67c03270 | ||
|
|
34c80bda7b | ||
|
|
ea86ff00db | ||
|
|
e73176f806 | ||
|
|
ca20267307 | ||
|
|
a31bcc2971 | ||
|
|
761b382364 | ||
|
|
79268a61ff | ||
|
|
6c6ee120ce | ||
|
|
d07c32556b | ||
|
|
7c1da23da0 | ||
|
|
9fecd18519 | ||
|
|
f5708c6dc6 |
@@ -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
|
||||
- Connects to RadioDJ REST API to acquire song info
|
||||
- Displays title, artist, and album (with scrolling marquee for long text)
|
||||
- Shows album art or a fallback image if none is available
|
||||
- Exports title, artist, album, and album art to output folder
|
||||
- Supports multiple profiles for different RadioDJ instances
|
||||
- Auto-regenerates missing `profiles.json` and fallback image
|
||||
## 🚀 New in v1.8.0: The Web Update
|
||||
* **Integrated Web Server:** Hosts a live, high-performance web widget for OBS Browser Sources.
|
||||
* **Tri-State Status Indicator:**
|
||||
* 🔴 **Red:** Disconnected from RadioDJ API.
|
||||
* 🟢 **Green:** Connected to API (Web Server Off).
|
||||
* 🔵 **Blue:** Full Broadcast Mode (API Connected + Web Server Live).
|
||||
* **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
|
||||
- Windows 7 or later
|
||||
- .NET Framework 4.7.2
|
||||
- RadioDJ REST API running and accessible
|
||||
## 🛠 Features
|
||||
* Connects to RadioDJ REST API to acquire real-time song info.
|
||||
* Displays title, artist, and album (with scrolling marquee for long text).
|
||||
* 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
|
||||
1. Download the release ZIP and extract all files to a folder.
|
||||
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`.
|
||||
## 🌐 Web API Endpoints
|
||||
Once the server is active (Blue Status), you can access the following data URLs locally or over your network:
|
||||
|
||||
## Usage
|
||||
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.
|
||||
3. Output files will be saved in the selected output folder.
|
||||
4. If album art is missing, a fallback image will be used.
|
||||
| Resource | URL (Localhost) | Purpose |
|
||||
| :--- | :--- | :--- |
|
||||
| **Main Widget** | `http://127.0.0.1:8181/` | Loads the full HTML/CSS/JS widget for OBS. |
|
||||
| **Song Title** | `http://127.0.0.1:8181/title.txt` | Returns the raw text of the current song. |
|
||||
| **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
|
||||
This project is licensed under the MIT License.
|
||||
*Note: Replace `127.0.0.1` with your computer's local IP address to access these from other devices.*
|
||||
|
||||
## Repository
|
||||
[https://git.smartcraft.me/minster586/RadioDJViewer](https://git.smartcraft.me/minster586/RadioDJViewer)
|
||||
## 📋 Requirements
|
||||
* 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.
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+140
-31
@@ -43,6 +43,8 @@
|
||||
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
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.label5 = new System.Windows.Forms.Label();
|
||||
this.label4 = new System.Windows.Forms.Label();
|
||||
@@ -53,6 +55,12 @@
|
||||
this.button2 = new System.Windows.Forms.Button();
|
||||
this.uicolor = new System.Windows.Forms.Button();
|
||||
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.menuStrip2.SuspendLayout();
|
||||
this.groupBox1.SuspendLayout();
|
||||
@@ -66,9 +74,9 @@
|
||||
this.toolStripStatusLabel2,
|
||||
this.toolStripStatusLabel4,
|
||||
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.Size = new System.Drawing.Size(612, 25);
|
||||
this.statusStrip1.Size = new System.Drawing.Size(612, 26);
|
||||
this.statusStrip1.TabIndex = 0;
|
||||
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.Name = "toolStripStatusLabel1";
|
||||
this.toolStripStatusLabel1.Size = new System.Drawing.Size(57, 20);
|
||||
this.toolStripStatusLabel1.Size = new System.Drawing.Size(64, 21);
|
||||
this.toolStripStatusLabel1.Text = "Status:";
|
||||
//
|
||||
// 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.Size = new System.Drawing.Size(16, 20);
|
||||
this.toolStripStatusLabel2.Size = new System.Drawing.Size(12, 21);
|
||||
this.toolStripStatusLabel2.Text = " ";
|
||||
//
|
||||
// toolStripStatusLabel4
|
||||
//
|
||||
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.Size = new System.Drawing.Size(262, 20);
|
||||
this.toolStripStatusLabel4.Size = new System.Drawing.Size(260, 21);
|
||||
this.toolStripStatusLabel4.Spring = true;
|
||||
this.toolStripStatusLabel4.Text = "Profile:";
|
||||
this.toolStripStatusLabel4.Click += new System.EventHandler(this.toolStripStatusLabel4_Click);
|
||||
@@ -97,7 +107,7 @@
|
||||
// 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.Text = "Song Changed Updateing";
|
||||
this.toolStripStatusLabel3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
@@ -109,7 +119,7 @@
|
||||
this.aboutToolStripMenuItem});
|
||||
this.menuStrip2.Location = new System.Drawing.Point(0, 0);
|
||||
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.Text = "menuStrip2";
|
||||
//
|
||||
@@ -120,7 +130,7 @@
|
||||
this.loggingToolStripMenuItem,
|
||||
this.exitToolStripMenuItem});
|
||||
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";
|
||||
//
|
||||
// restAPISettingToolStripMenuItem
|
||||
@@ -137,19 +147,19 @@
|
||||
this.offToolStripMenuItem});
|
||||
this.loggingToolStripMenuItem.Enabled = false;
|
||||
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";
|
||||
//
|
||||
// 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";
|
||||
//
|
||||
// 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";
|
||||
//
|
||||
// exitToolStripMenuItem
|
||||
@@ -162,12 +172,17 @@
|
||||
// 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.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
|
||||
//
|
||||
// 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.label5);
|
||||
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.Location = new System.Drawing.Point(199, 37);
|
||||
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.TabStop = false;
|
||||
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
|
||||
//
|
||||
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.Size = new System.Drawing.Size(266, 17);
|
||||
this.label6.TabIndex = 5;
|
||||
this.label6.Text = "No album";
|
||||
this.label6.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// 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.Size = new System.Drawing.Size(266, 17);
|
||||
this.label5.TabIndex = 4;
|
||||
this.label5.Text = "No artist";
|
||||
this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// 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.Size = new System.Drawing.Size(266, 16);
|
||||
this.label4.TabIndex = 3;
|
||||
this.label4.Text = "No title";
|
||||
this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
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.Location = new System.Drawing.Point(23, 126);
|
||||
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, 116);
|
||||
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.Text = "Album:";
|
||||
//
|
||||
// label2
|
||||
//
|
||||
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.Location = new System.Drawing.Point(23, 80);
|
||||
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, 75);
|
||||
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.Text = "Artist:";
|
||||
//
|
||||
// label1
|
||||
//
|
||||
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.Location = new System.Drawing.Point(23, 35);
|
||||
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, 29);
|
||||
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.Text = "Title:";
|
||||
//
|
||||
// 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.Size = new System.Drawing.Size(99, 42);
|
||||
this.button1.TabIndex = 5;
|
||||
@@ -248,7 +288,7 @@
|
||||
//
|
||||
// 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.Size = new System.Drawing.Size(99, 42);
|
||||
this.button2.TabIndex = 6;
|
||||
@@ -259,7 +299,7 @@
|
||||
// uicolor
|
||||
//
|
||||
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.Size = new System.Drawing.Size(28, 23);
|
||||
this.uicolor.TabIndex = 7;
|
||||
@@ -267,17 +307,78 @@
|
||||
//
|
||||
// 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.Size = new System.Drawing.Size(180, 173);
|
||||
this.pictureBox1.TabIndex = 3;
|
||||
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
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
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.button2);
|
||||
this.Controls.Add(this.button1);
|
||||
@@ -326,6 +427,14 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem onToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem offToolStripMenuItem;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+757
-35
File diff suppressed because it is too large
Load Diff
@@ -120,17 +120,10 @@
|
||||
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</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">
|
||||
<value>247, 17</value>
|
||||
</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">
|
||||
<value>
|
||||
AAABAAEAIBgAAAEAIACIDAAAFgAAACgAAAAgAAAAMAAAAAEAIAAAAAAAAAwAAMMOAADDDgAAAAAAAAAA
|
||||
|
||||
@@ -20,6 +20,12 @@ namespace RadioDJViewer
|
||||
public string MarqueeScrollSpeed { get; set; } // "Very Slow", etc.
|
||||
public int MarqueePauseTime { get; set; } // in seconds
|
||||
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 bool ToastNotificationsEnabled { get; set; }
|
||||
}
|
||||
|
||||
public static class ProfileStorage
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace RadioDJViewer
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern int SetCurrentProcessExplicitAppUserModelID(string AppID);
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
try
|
||||
{
|
||||
SetCurrentProcessExplicitAppUserModelID("RadioDJViewer.App");
|
||||
}
|
||||
catch { }
|
||||
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new Main());
|
||||
|
||||
@@ -6,9 +6,9 @@ using System.Runtime.InteropServices;
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("RadioDJViewer")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyDescription("RadioDJ Metadata OBS Extractor")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyCompany("Smartcraft Media Group")]
|
||||
[assembly: AssemblyProduct("RadioDJViewer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion("1.1.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.0")]
|
||||
[assembly: AssemblyVersion("1.14.0")]
|
||||
[assembly: AssemblyFileVersion("1.14.0")]
|
||||
|
||||
@@ -13,6 +13,21 @@
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<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 Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@@ -53,6 +68,7 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="EmbeddedResourceHelper.cs" />
|
||||
<Compile Include="ProfileStorage.cs" />
|
||||
<Compile Include="radiodj-restapi-template.cs">
|
||||
<SubType>Form</SubType>
|
||||
@@ -68,6 +84,7 @@
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="WidgetServer.cs" />
|
||||
<EmbeddedResource Include="radiodj-restapi-template.resx">
|
||||
<DependentUpon>radiodj-restapi-template.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
@@ -109,8 +126,23 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="icon.ico" />
|
||||
<EmbeddedResource Include="index.html" />
|
||||
<EmbeddedResource Include="script.js" />
|
||||
<EmbeddedResource Include="style.css" />
|
||||
<None Include="Resources\MdiWeatherSunny.png" />
|
||||
<None Include="Resources\MdiWeatherNight.png" />
|
||||
</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" />
|
||||
</Project>
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
+153
-38
@@ -32,7 +32,7 @@
|
||||
this.groupBox1 = new System.Windows.Forms.GroupBox();
|
||||
this.textBox1 = new System.Windows.Forms.TextBox();
|
||||
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.comboBox1 = new System.Windows.Forms.ComboBox();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
@@ -51,12 +51,22 @@
|
||||
this.label10 = new System.Windows.Forms.Label();
|
||||
this.label11 = new System.Windows.Forms.Label();
|
||||
this.textBox6 = new System.Windows.Forms.TextBox();
|
||||
this.button5 = new System.Windows.Forms.Button();
|
||||
this.button6 = new System.Windows.Forms.Button();
|
||||
this.label6 = new System.Windows.Forms.Label();
|
||||
this.label7 = new System.Windows.Forms.Label();
|
||||
this.textBox7 = new System.Windows.Forms.TextBox();
|
||||
this.groupBox2 = new System.Windows.Forms.GroupBox();
|
||||
this.toast_box_group_box = new System.Windows.Forms.GroupBox();
|
||||
this.toast_check_box_enable = new System.Windows.Forms.CheckBox();
|
||||
this.toast_label = new System.Windows.Forms.Label();
|
||||
this.web_server_settings_groupbox = new System.Windows.Forms.GroupBox();
|
||||
this.clock_visibility_checkBox = new System.Windows.Forms.CheckBox();
|
||||
this.clock_label = new System.Windows.Forms.Label();
|
||||
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.textBoxPauseTime = new System.Windows.Forms.TextBox();
|
||||
this.separatingcharacterformatbox = new System.Windows.Forms.TextBox();
|
||||
@@ -64,10 +74,11 @@
|
||||
this.label9 = new System.Windows.Forms.Label();
|
||||
this.label8 = new System.Windows.Forms.Label();
|
||||
this.label12 = new System.Windows.Forms.Label();
|
||||
this.label13 = new System.Windows.Forms.Label();
|
||||
this.groupBox1.SuspendLayout();
|
||||
this.groupBox3.SuspendLayout();
|
||||
this.groupBox2.SuspendLayout();
|
||||
this.toast_box_group_box.SuspendLayout();
|
||||
this.web_server_settings_groupbox.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// groupBox1
|
||||
@@ -97,18 +108,18 @@
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "URL Format:";
|
||||
//
|
||||
// button1
|
||||
// ok_button_master
|
||||
//
|
||||
this.button1.Location = new System.Drawing.Point(35, 584);
|
||||
this.button1.Name = "button1";
|
||||
this.button1.Size = new System.Drawing.Size(157, 23);
|
||||
this.button1.TabIndex = 1;
|
||||
this.button1.Text = "Save";
|
||||
this.button1.UseVisualStyleBackColor = true;
|
||||
this.ok_button_master.Location = new System.Drawing.Point(54, 729);
|
||||
this.ok_button_master.Name = "ok_button_master";
|
||||
this.ok_button_master.Size = new System.Drawing.Size(157, 23);
|
||||
this.ok_button_master.TabIndex = 1;
|
||||
this.ok_button_master.Text = "OK";
|
||||
this.ok_button_master.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// button2
|
||||
//
|
||||
this.button2.Location = new System.Drawing.Point(357, 584);
|
||||
this.button2.Location = new System.Drawing.Point(302, 729);
|
||||
this.button2.Name = "button2";
|
||||
this.button2.Size = new System.Drawing.Size(157, 23);
|
||||
this.button2.TabIndex = 2;
|
||||
@@ -182,7 +193,7 @@
|
||||
//
|
||||
// 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.Size = new System.Drawing.Size(120, 30);
|
||||
this.button3.TabIndex = 8;
|
||||
@@ -191,7 +202,7 @@
|
||||
//
|
||||
// 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.Size = new System.Drawing.Size(120, 30);
|
||||
this.button4.TabIndex = 9;
|
||||
@@ -204,7 +215,7 @@
|
||||
this.groupBox3.Controls.Add(this.labelOutputFolderPath);
|
||||
this.groupBox3.Controls.Add(this.textBox5);
|
||||
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.Size = new System.Drawing.Size(482, 115);
|
||||
this.groupBox3.TabIndex = 12;
|
||||
@@ -261,15 +272,6 @@
|
||||
this.textBox6.Size = new System.Drawing.Size(290, 20);
|
||||
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
|
||||
//
|
||||
this.button6.Location = new System.Drawing.Point(23, 269);
|
||||
@@ -306,6 +308,8 @@
|
||||
//
|
||||
// groupBox2
|
||||
//
|
||||
this.groupBox2.Controls.Add(this.toast_box_group_box);
|
||||
this.groupBox2.Controls.Add(this.web_server_settings_groupbox);
|
||||
this.groupBox2.Controls.Add(this.label13);
|
||||
this.groupBox2.Controls.Add(this.comboBoxScrollSpeed);
|
||||
this.groupBox2.Controls.Add(this.textBoxPauseTime);
|
||||
@@ -319,7 +323,6 @@
|
||||
this.groupBox2.Controls.Add(this.label7);
|
||||
this.groupBox2.Controls.Add(this.label6);
|
||||
this.groupBox2.Controls.Add(this.button6);
|
||||
this.groupBox2.Controls.Add(this.button5);
|
||||
this.groupBox2.Controls.Add(this.groupBox3);
|
||||
this.groupBox2.Controls.Add(this.button4);
|
||||
this.groupBox2.Controls.Add(this.button3);
|
||||
@@ -333,11 +336,119 @@
|
||||
this.groupBox2.Controls.Add(this.comboBox1);
|
||||
this.groupBox2.Location = new System.Drawing.Point(12, 100);
|
||||
this.groupBox2.Name = "groupBox2";
|
||||
this.groupBox2.Size = new System.Drawing.Size(519, 470);
|
||||
this.groupBox2.Size = new System.Drawing.Size(519, 623);
|
||||
this.groupBox2.TabIndex = 3;
|
||||
this.groupBox2.TabStop = false;
|
||||
this.groupBox2.Text = "Profiles";
|
||||
//
|
||||
// toast_box_group_box
|
||||
//
|
||||
this.toast_box_group_box.Controls.Add(this.toast_check_box_enable);
|
||||
this.toast_box_group_box.Controls.Add(this.toast_label);
|
||||
this.toast_box_group_box.Location = new System.Drawing.Point(133, 575);
|
||||
this.toast_box_group_box.Name = "toast_box_group_box";
|
||||
this.toast_box_group_box.Size = new System.Drawing.Size(248, 42);
|
||||
this.toast_box_group_box.TabIndex = 4;
|
||||
this.toast_box_group_box.TabStop = false;
|
||||
this.toast_box_group_box.Text = "Windows Toast";
|
||||
//
|
||||
// toast_check_box_enable
|
||||
//
|
||||
this.toast_check_box_enable.AutoSize = true;
|
||||
this.toast_check_box_enable.Location = new System.Drawing.Point(130, 17);
|
||||
this.toast_check_box_enable.Name = "toast_check_box_enable";
|
||||
this.toast_check_box_enable.Size = new System.Drawing.Size(59, 17);
|
||||
this.toast_check_box_enable.TabIndex = 1;
|
||||
this.toast_check_box_enable.Text = "Enable";
|
||||
this.toast_check_box_enable.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// toast_label
|
||||
//
|
||||
this.toast_label.AutoSize = true;
|
||||
this.toast_label.Location = new System.Drawing.Point(40, 20);
|
||||
this.toast_label.Name = "toast_label";
|
||||
this.toast_label.Size = new System.Drawing.Size(73, 13);
|
||||
this.toast_label.TabIndex = 0;
|
||||
this.toast_label.Text = "Toast Control:";
|
||||
//
|
||||
// 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";
|
||||
//
|
||||
// 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;
|
||||
//
|
||||
// clock_label
|
||||
//
|
||||
this.clock_label.AutoSize = true;
|
||||
this.clock_label.Location = new System.Drawing.Point(44, 56);
|
||||
this.clock_label.Name = "clock_label";
|
||||
this.clock_label.Size = new System.Drawing.Size(76, 13);
|
||||
this.clock_label.TabIndex = 37;
|
||||
this.clock_label.Text = "Clock Visibility:";
|
||||
//
|
||||
// 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
|
||||
//
|
||||
this.comboBoxScrollSpeed.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
@@ -402,23 +513,14 @@
|
||||
this.label12.TabIndex = 27;
|
||||
this.label12.Text = "Separating character format";
|
||||
//
|
||||
// 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";
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(543, 619);
|
||||
this.ClientSize = new System.Drawing.Size(553, 764);
|
||||
this.Controls.Add(this.groupBox2);
|
||||
this.Controls.Add(this.button2);
|
||||
this.Controls.Add(this.button1);
|
||||
this.Controls.Add(this.ok_button_master);
|
||||
this.Controls.Add(this.groupBox1);
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "Form1";
|
||||
@@ -429,6 +531,10 @@
|
||||
this.groupBox3.PerformLayout();
|
||||
this.groupBox2.ResumeLayout(false);
|
||||
this.groupBox2.PerformLayout();
|
||||
this.toast_box_group_box.ResumeLayout(false);
|
||||
this.toast_box_group_box.PerformLayout();
|
||||
this.web_server_settings_groupbox.ResumeLayout(false);
|
||||
this.web_server_settings_groupbox.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
@@ -438,7 +544,7 @@
|
||||
private System.Windows.Forms.GroupBox groupBox1;
|
||||
private System.Windows.Forms.TextBox textBox1;
|
||||
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.ComboBox comboBox1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
@@ -457,7 +563,6 @@
|
||||
private System.Windows.Forms.Label label10;
|
||||
private System.Windows.Forms.Label label11;
|
||||
private System.Windows.Forms.TextBox textBox6;
|
||||
private System.Windows.Forms.Button button5;
|
||||
private System.Windows.Forms.Button button6;
|
||||
private System.Windows.Forms.Label label6;
|
||||
private System.Windows.Forms.Label label7;
|
||||
@@ -473,5 +578,15 @@
|
||||
private System.Windows.Forms.TextBox textBoxPauseTime;
|
||||
private System.Windows.Forms.TextBox separatingcharacterformatbox;
|
||||
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;
|
||||
private System.Windows.Forms.GroupBox toast_box_group_box;
|
||||
private System.Windows.Forms.CheckBox toast_check_box_enable;
|
||||
private System.Windows.Forms.Label toast_label;
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,8 @@ namespace RadioDJViewer
|
||||
this.buttonSelectOutputFolder.Click += ButtonSelectOutputFolder_Click;
|
||||
this.button6.Click += ButtonSelectMainImagesFolder_Click;
|
||||
this.button3.Click += button3_Click;
|
||||
this.button1.Click += button1_Click;
|
||||
this.ok_button_master.Click += button1_Click;
|
||||
this.button4.Click += button4_Click;
|
||||
this.button5.Click += button5_Click;
|
||||
this.button2.Click += button2_Click;
|
||||
// Populate profile list on open
|
||||
LoadProfileList();
|
||||
@@ -79,6 +78,16 @@ namespace RadioDJViewer
|
||||
comboBoxScrollSpeed.SelectedItem = profile.MarqueeScrollSpeed ?? "Medium";
|
||||
textBoxPauseTime.Text = profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime.ToString() : "6";
|
||||
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;
|
||||
toast_check_box_enable.Checked = profile.ToastNotificationsEnabled;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,7 +139,13 @@ namespace RadioDJViewer
|
||||
PollingRateSeconds = pollingRate,
|
||||
MarqueeScrollSpeed = comboBoxScrollSpeed.SelectedItem?.ToString() ?? "Medium",
|
||||
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,
|
||||
ToastNotificationsEnabled = toast_check_box_enable.Checked
|
||||
};
|
||||
ProfileStorage.SaveProfile(profile);
|
||||
// Refresh profile list
|
||||
@@ -152,33 +167,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)
|
||||
{
|
||||
// Save (bottom Save button) - save all fields to the currently selected or new profile
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
})();
|
||||
@@ -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 |
Reference in New Issue
Block a user