From 34c80bda7b68cff107ba189857cddb3e9eeebfb5 Mon Sep 17 00:00:00 2001
From: minster586 <43217359+minster586@users.noreply.github.com>
Date: Wed, 1 Jul 2026 22:37:18 -0400
Subject: [PATCH] Set Time Clock
---
RadioDJViewer/Main.Designer.cs | 129 ++++++---
RadioDJViewer/Main.cs | 214 +++++++++++---
RadioDJViewer/Main.resx | 9 +-
RadioDJViewer/ProfileStorage.cs | 2 +
RadioDJViewer/Properties/AssemblyInfo.cs | 4 +-
RadioDJViewer/WidgetServer.cs | 79 ++++-
RadioDJViewer/index.html | 5 +-
.../radiodj-restapi-template.Designer.cs | 137 +++++----
RadioDJViewer/radiodj-restapi-template.cs | 6 +-
RadioDJViewer/script.js | 271 ++++++++++--------
RadioDJViewer/style.css | 34 +++
11 files changed, 623 insertions(+), 267 deletions(-)
diff --git a/RadioDJViewer/Main.Designer.cs b/RadioDJViewer/Main.Designer.cs
index 8912054..08bccda 100644
--- a/RadioDJViewer/Main.Designer.cs
+++ b/RadioDJViewer/Main.Designer.cs
@@ -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();
@@ -56,8 +58,9 @@
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.label7 = new System.Windows.Forms.Label();
- this.label8 = new System.Windows.Forms.Label();
+ 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();
@@ -71,9 +74,9 @@
this.toolStripStatusLabel2,
this.toolStripStatusLabel4,
this.toolStripStatusLabel3});
- this.statusStrip1.Location = new System.Drawing.Point(0, 314);
+ 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";
//
@@ -81,23 +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.Name = "toolStripStatusLabel2";
- this.toolStripStatusLabel2.Size = new System.Drawing.Size(16, 20);
- // Use BackColor only to indicate status. Text is a single space so the label renders as a square.
- this.toolStripStatusLabel2.Text = " ";
- this.toolStripStatusLabel2.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
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(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);
@@ -105,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;
@@ -117,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";
//
@@ -128,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
@@ -151,13 +153,13 @@
// 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
@@ -170,12 +172,15 @@
// 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);
@@ -187,15 +192,34 @@
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.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, 140);
+ 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;
@@ -205,7 +229,7 @@
// label5
//
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, 84);
+ 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;
@@ -226,7 +250,7 @@
//
this.label3.AutoSize = true;
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, 138);
+ this.label3.Location = new System.Drawing.Point(23, 116);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(76, 24);
this.label3.TabIndex = 2;
@@ -236,7 +260,7 @@
//
this.label2.AutoSize = true;
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, 82);
+ this.label2.Location = new System.Drawing.Point(23, 75);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(62, 24);
this.label2.TabIndex = 1;
@@ -254,7 +278,7 @@
//
// button1
//
- this.button1.Location = new System.Drawing.Point(62, 230);
+ 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;
@@ -264,7 +288,7 @@
//
// button2
//
- this.button2.Location = new System.Drawing.Point(180, 230);
+ 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;
@@ -275,7 +299,7 @@
// uicolor
//
this.uicolor.Image = global::RadioDJViewer.Properties.Resources.MdiWeatherNight;
- this.uicolor.Location = new System.Drawing.Point(572, 276);
+ 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;
@@ -283,7 +307,7 @@
//
// 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;
@@ -292,7 +316,7 @@
// profile_drop_box
//
this.profile_drop_box.FormattingEnabled = true;
- this.profile_drop_box.Location = new System.Drawing.Point(62, 278);
+ 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;
@@ -300,7 +324,7 @@
// profile_label
//
this.profile_label.AutoSize = true;
- this.profile_label.Location = new System.Drawing.Point(12, 281);
+ 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;
@@ -309,37 +333,49 @@
//
// load_profile_button
//
- this.load_profile_button.Location = new System.Drawing.Point(285, 276);
+ 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;
//
- // label7
+ // time_label
//
- 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 = "――――――――――――――――――――――――――――――――――――――――――――――――――";
+ 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);
//
- // label8
+ // label9
//
- this.label8.AutoSize = true;
- this.label8.Location = new System.Drawing.Point(24, 118);
- 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;
+ 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, 339);
+ 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);
@@ -396,6 +432,9 @@
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;
}
}
diff --git a/RadioDJViewer/Main.cs b/RadioDJViewer/Main.cs
index 50428e3..8143543 100644
--- a/RadioDJViewer/Main.cs
+++ b/RadioDJViewer/Main.cs
@@ -11,12 +11,18 @@ using System.Xml;
using System.Threading;
using System.IO;
using System.Resources;
+using System.Globalization;
namespace RadioDJViewer
{
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 outputFolderPath = string.Empty;
private bool isConnected = false;
@@ -31,6 +37,7 @@ namespace RadioDJViewer
// Timer to poll REST API
private System.Windows.Forms.Timer apiTimer;
+ private System.Windows.Forms.Timer playbackTimer;
private string lastApiXml = null;
private System.Windows.Forms.Timer marqueeTimerTitle;
@@ -63,6 +70,9 @@ namespace RadioDJViewer
apiTimer = new System.Windows.Forms.Timer();
apiTimer.Interval = 3000; // 3 seconds
apiTimer.Tick += ApiTimer_Tick;
+ playbackTimer = new System.Windows.Forms.Timer();
+ playbackTimer.Interval = 250;
+ playbackTimer.Tick += PlaybackTimer_Tick;
// Marquee timers for each label
marqueeTimerTitle = new System.Windows.Forms.Timer();
marqueeTimerTitle.Interval = marqueeScrollSpeed;
@@ -275,22 +285,18 @@ namespace RadioDJViewer
if (response.IsSuccessStatusCode)
{
var xml = await response.Content.ReadAsStringAsync();
- if (xml != lastApiXml)
+ lastApiXml = xml;
+ ParseAndDisplaySongInfo(xml);
+ try
{
- lastApiXml = xml;
- ParseAndDisplaySongInfo(xml);
- // Build a key from Artist and Title and only show change if different
- try
+ var newKey = $"{marqueeTextArtist}|{marqueeTextTitle}";
+ if (!string.Equals(newKey, _currentTrackKey, StringComparison.Ordinal))
{
- var newKey = $"{marqueeTextArtist}|{marqueeTextTitle}";
- if (!string.Equals(newKey, _currentTrackKey, StringComparison.Ordinal))
- {
- _currentTrackKey = newKey;
- ShowTemporaryStatus("Song Change Detected", 2000);
- }
+ _currentTrackKey = newKey;
+ ShowTemporaryStatus("Song Change Detected", 2000);
}
- catch { }
}
+ catch { }
}
}
catch { }
@@ -332,6 +338,16 @@ namespace RadioDJViewer
// Disconnect
isConnected = false;
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
{
@@ -492,6 +508,82 @@ namespace RadioDJViewer
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)
{
try
@@ -503,12 +595,17 @@ namespace RadioDJViewer
string artist = doc.SelectSingleNode("//*[translate(local-name(), 'ARTIST', 'artist')='artist']")?.InnerText;
string album = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUM', 'album')='album']")?.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";
marqueeTextArtist = !string.IsNullOrWhiteSpace(artist) ? artist : "No artist";
marqueeTextAlbum = !string.IsNullOrWhiteSpace(album) ? album : "No album";
StartMarqueeTimers();
+ UpdatePlaybackState($"{marqueeTextArtist}|{marqueeTextTitle}", elapsed, duration);
+
// Write to output files
if (!string.IsNullOrEmpty(outputFolderPath))
{
@@ -522,6 +619,7 @@ namespace RadioDJViewer
{
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))
@@ -576,32 +674,38 @@ namespace RadioDJViewer
marqueeTextArtist = "No artist";
marqueeTextAlbum = "No album";
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;
}
}
private void UpdateStatusBar()
{
- // Update color box based on connection states
try
{
if (!isConnected)
{
- toolStripStatusLabel2.BackColor = Color.Red; // disconnected/error
+ toolStripStatusLabel2.BackColor = Color.Red;
}
else
{
if (isConnected && widgetRunning)
- toolStripStatusLabel2.BackColor = Color.Blue; // both running
+ toolStripStatusLabel2.BackColor = Color.Blue;
else
- toolStripStatusLabel2.BackColor = Color.Green; // API ok, web off
+ toolStripStatusLabel2.BackColor = Color.Green;
}
}
catch { }
- // Profile
toolStripStatusLabel4.Text = $"Profile: {currentProfile}";
- // If there's no active temporary message, ensure label3 shows default
if (statusCts == null)
{
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected";
@@ -610,7 +714,6 @@ namespace RadioDJViewer
private void ShowTemporaryStatus(string message, int milliseconds)
{
- // Cancel any previous temporary status
try
{
statusCts?.Cancel();
@@ -620,7 +723,6 @@ namespace RadioDJViewer
if (milliseconds <= 0)
{
- // Persistent message until changed - keep a non-null token so UpdateStatusBar doesn't overwrite
var persistent = new CancellationTokenSource();
statusCts = persistent;
toolStripStatusLabel3.Text = message;
@@ -638,7 +740,6 @@ namespace RadioDJViewer
await Task.Delay(milliseconds, cts.Token);
if (!cts.IsCancellationRequested)
{
- // clear temporary message
statusCts = null;
this.BeginInvoke((Action)(() => {
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected";
@@ -658,12 +759,13 @@ namespace RadioDJViewer
private void StartWidgetServerIfEnabled()
{
- // Called after API connection is confirmed
if (loadedProfile == null || !loadedProfile.WebServerEnabled)
return;
try
{
+ WidgetServer.CachedClockVisibility = loadedProfile.ClockVisibilityEnabled;
+
if (widgetServer == null)
{
int port = loadedProfile.WebServerPort > 0 ? loadedProfile.WebServerPort : 8080;
@@ -678,16 +780,14 @@ namespace RadioDJViewer
{
widgetRunning = false;
UpdateStatusBar();
- // Show error message
ShowTemporaryStatus($"Web server error: {ex.Message}", 5000);
}
}
private void restAPISettingToolStripMenuItem_Click(object sender, EventArgs e)
{
- // Open settings/profile dialog
var settingsForm = new Form1();
- settingsForm.ApplyTheme(isDarkMode); // Pass current color mode
+ settingsForm.ApplyTheme(isDarkMode);
settingsForm.ShowDialog();
}
@@ -708,32 +808,28 @@ namespace RadioDJViewer
private void toolStripStatusLabel4_Click(object sender, EventArgs e)
{
- // Profile click handler (to be implemented)
}
public void LoadProfile(Profile profile)
{
- // Update main form fields from profile
currentProfile = profile.Name;
selectedFolderPath = profile.OutputFolder;
outputFolderPath = profile.OutputFolder;
- mainImagesFolderPath = profile.MainImagesFolder; // Update new field
- loadedProfile = profile; // Store loaded profile for REST API connection
- // Update polling rate when profile is loaded
+ mainImagesFolderPath = profile.MainImagesFolder;
+ loadedProfile = profile;
int pollingRate = profile.PollingRateSeconds > 0 ? profile.PollingRateSeconds : 3;
apiTimer.Interval = pollingRate * 1000;
SetMarqueeScrollSpeed(profile.MarqueeScrollSpeed ?? "Medium");
SetMarqueePauseTime(profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime : 6);
SetMarqueeSeparator(profile.MarqueeSeparator ?? " | ");
- // Immediately apply marquee settings to timers
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();
- // You can add more logic to update other fields if needed
}
private void Uicolor_Click(object sender, EventArgs e)
@@ -759,23 +855,19 @@ namespace RadioDJViewer
private void UpdateTheme()
{
- // Set icon
Bitmap icon = isDarkMode ? Properties.Resources.MdiWeatherSunny : Properties.Resources.MdiWeatherNight;
- // Invert sun icon for visibility in dark mode
if (isDarkMode && icon != null)
{
icon = InvertIcon(icon);
}
- // Resize icon to fit button and center it
if (icon != null)
{
int btnW = uicolor.Width;
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.ImageAlign = ContentAlignment.MiddleCenter;
}
- // Set colors
Color backColor = isDarkMode ? Color.FromArgb(32, 32, 32) : SystemColors.Control;
Color foreColor = isDarkMode ? Color.White : SystemColors.ControlText;
this.BackColor = backColor;
@@ -798,7 +890,10 @@ namespace RadioDJViewer
private void label7_Click(object sender, EventArgs e)
{
+ }
+ private void label9_Click(object sender, EventArgs e)
+ {
}
public string GetCurrentProfileName()
@@ -811,7 +906,6 @@ namespace RadioDJViewer
profile_drop_box.Items.Clear();
var profileNames = ProfileStorage.GetProfileNames();
profile_drop_box.Items.AddRange(profileNames.ToArray());
- // Set selected item to current profile if available
string currentProfile = GetCurrentProfileName();
if (!string.IsNullOrEmpty(currentProfile) && profileNames.Contains(currentProfile))
profile_drop_box.SelectedItem = currentProfile;
@@ -832,5 +926,49 @@ namespace RadioDJViewer
}
}
}
+
+ 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}";
+ }
}
}
diff --git a/RadioDJViewer/Main.resx b/RadioDJViewer/Main.resx
index 1cf2200..9cb2abd 100644
--- a/RadioDJViewer/Main.resx
+++ b/RadioDJViewer/Main.resx
@@ -120,17 +120,10 @@
17, 17
-
-
-
- iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAABGdBTUEAALGPC/xhBQAAAANQTFRFJn8A
- Mx1isQAAAAlwSFlzAAAOvAAADrwBlbxySQAAAB9JREFUaN7twTEBAAAAwqD1T20IX6AAAAAAAAAAAD4D
- IIAAAT2Y3yoAAAAASUVORK5CYII=
-
-
247, 17
+
AAABAAEAIBgAAAEAIACIDAAAFgAAACgAAAAgAAAAMAAAAAEAIAAAAAAAAAwAAMMOAADDDgAAAAAAAAAA
diff --git a/RadioDJViewer/ProfileStorage.cs b/RadioDJViewer/ProfileStorage.cs
index 34797f6..6759bf1 100644
--- a/RadioDJViewer/ProfileStorage.cs
+++ b/RadioDJViewer/ProfileStorage.cs
@@ -23,6 +23,8 @@ namespace RadioDJViewer
// 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
diff --git a/RadioDJViewer/Properties/AssemblyInfo.cs b/RadioDJViewer/Properties/AssemblyInfo.cs
index ef23503..35b4986 100644
--- a/RadioDJViewer/Properties/AssemblyInfo.cs
+++ b/RadioDJViewer/Properties/AssemblyInfo.cs
@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
-[assembly: AssemblyVersion("1.9.0")]
-[assembly: AssemblyFileVersion("1.9.0")]
+[assembly: AssemblyVersion("1.10.0")]
+[assembly: AssemblyFileVersion("1.10.0")]
diff --git a/RadioDJViewer/WidgetServer.cs b/RadioDJViewer/WidgetServer.cs
index 7667b57..84a4765 100644
--- a/RadioDJViewer/WidgetServer.cs
+++ b/RadioDJViewer/WidgetServer.cs
@@ -21,7 +21,12 @@ namespace RadioDJViewer
// 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 getArtist;
@@ -112,14 +117,35 @@ namespace RadioDJViewer
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("{{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;
@@ -127,11 +153,44 @@ namespace RadioDJViewer
}
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 ?? "");
@@ -209,6 +268,24 @@ namespace RadioDJViewer
}
}
+ 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();
diff --git a/RadioDJViewer/index.html b/RadioDJViewer/index.html
index 8e7abb8..a5d50da 100644
--- a/RadioDJViewer/index.html
+++ b/RadioDJViewer/index.html
@@ -25,8 +25,9 @@
-
Loading...
-
Loading...
+
{{TITLE}}
+
{{ARTIST}}
+
{{TIME}}
diff --git a/RadioDJViewer/radiodj-restapi-template.Designer.cs b/RadioDJViewer/radiodj-restapi-template.Designer.cs
index 83195f2..18e98e0 100644
--- a/RadioDJViewer/radiodj-restapi-template.Designer.cs
+++ b/RadioDJViewer/radiodj-restapi-template.Designer.cs
@@ -56,6 +56,11 @@
this.label7 = new System.Windows.Forms.Label();
this.textBox7 = new System.Windows.Forms.TextBox();
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.textBoxPauseTime = new System.Windows.Forms.TextBox();
@@ -64,11 +69,8 @@
this.label9 = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label();
this.label12 = new System.Windows.Forms.Label();
- this.web_server_label = new System.Windows.Forms.Label();
- this.web_server_checkbox = new System.Windows.Forms.CheckBox();
- this.web_server_settings_groupbox = new System.Windows.Forms.GroupBox();
- this.web_server_port_label = new System.Windows.Forms.Label();
- this.web_server_port_textbox = new System.Windows.Forms.TextBox();
+ this.clock_label = new System.Windows.Forms.Label();
+ this.clock_visibility_checkBox = new System.Windows.Forms.CheckBox();
this.groupBox1.SuspendLayout();
this.groupBox3.SuspendLayout();
this.groupBox2.SuspendLayout();
@@ -104,7 +106,7 @@
//
// ok_button_master
//
- this.ok_button_master.Location = new System.Drawing.Point(65, 648);
+ this.ok_button_master.Location = new System.Drawing.Point(65, 694);
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;
@@ -113,7 +115,7 @@
//
// button2
//
- this.button2.Location = new System.Drawing.Point(305, 648);
+ this.button2.Location = new System.Drawing.Point(305, 694);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(157, 23);
this.button2.TabIndex = 2;
@@ -187,7 +189,7 @@
//
// button3
//
- this.button3.Location = new System.Drawing.Point(71, 491);
+ 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;
@@ -196,7 +198,7 @@
//
// button4
//
- this.button4.Location = new System.Drawing.Point(316, 491);
+ 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;
@@ -209,7 +211,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(31, 370);
+ 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;
@@ -329,11 +331,61 @@
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, 527);
+ this.groupBox2.Size = new System.Drawing.Size(519, 575);
this.groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false;
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;
@@ -407,59 +459,30 @@
this.label12.TabIndex = 27;
this.label12.Text = "Separating character format";
//
- // web_server_label
+ // clock_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:";
+ 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_checkbox
+ // clock_visibility_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;
- //
- // web_server_settings_groupbox
- //
- 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, 298);
- this.web_server_settings_groupbox.Name = "web_server_settings_groupbox";
- this.web_server_settings_groupbox.Size = new System.Drawing.Size(396, 58);
- 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_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_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;
+ 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
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(572, 695);
+ this.ClientSize = new System.Drawing.Size(572, 729);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.button2);
this.Controls.Add(this.ok_button_master);
@@ -523,5 +546,7 @@
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;
}
}
\ No newline at end of file
diff --git a/RadioDJViewer/radiodj-restapi-template.cs b/RadioDJViewer/radiodj-restapi-template.cs
index 1dd75d9..f9e4e2e 100644
--- a/RadioDJViewer/radiodj-restapi-template.cs
+++ b/RadioDJViewer/radiodj-restapi-template.cs
@@ -83,6 +83,8 @@ namespace RadioDJViewer
{
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 { }
}
@@ -139,7 +141,9 @@ namespace RadioDJViewer
MarqueeSeparator = separatingcharacterformatbox.Text,
// Web server settings
WebServerEnabled = web_server_checkbox.Checked,
- WebServerPort = int.TryParse(web_server_port_textbox.Text, out int wp) ? wp : 8080
+ WebServerPort = int.TryParse(web_server_port_textbox.Text, out int wp) ? wp : 8080,
+ // Clock visibility setting
+ ClockVisibilityEnabled = clock_visibility_checkBox.Checked
};
ProfileStorage.SaveProfile(profile);
// Refresh profile list
diff --git a/RadioDJViewer/script.js b/RadioDJViewer/script.js
index cb437ae..e7fe24a 100644
--- a/RadioDJViewer/script.js
+++ b/RadioDJViewer/script.js
@@ -1,128 +1,171 @@
-///////////////
-// PARAMETERS //
-///////////////
+(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;
-const queryString = window.location.search;
-const urlParams = new URLSearchParams(queryString);
-const refreshRate = parseInt(urlParams.get("refreshrate")) || 6; // Default 6 seconds
-
-// Image filename - update this if the backend changes the image path
-const IMAGE_FILENAME = "Album-Art.png";
-
-//////////////////
-// GLOBAL STATE //
-//////////////////
-
-let currentArtist = "";
-let currentTitle = "";
-let refreshInterval = null;
-
-//////////////////
-// REFRESH LOGIC //
-//////////////////
-
-/**
- * Fetches the artist and title from text files
- */
-async function fetchSongData() {
- try {
- const artistResponse = await fetch("artist.txt");
- const titleResponse = await fetch("title.txt");
-
- if (!artistResponse.ok || !titleResponse.ok) {
- console.error("Failed to fetch song data");
- return null;
+ function getTextContent(selector) {
+ var element = document.querySelector(selector);
+ if (!element || typeof element.textContent !== "string") {
+ return "";
}
-
- const artist = (await artistResponse.text()).trim();
- const title = (await titleResponse.text()).trim();
-
- return { artist, title };
- } catch (error) {
- console.error("Error fetching song data:", error);
- return null;
- }
-}
-
-/**
- * Updates the DOM elements if the song data has changed
- */
-function updateSongDisplay(artist, title) {
- const titleElement = document.querySelector(".song-title");
- const artistElement = document.querySelector(".song-artist");
-
- let trackChanged = false;
-
- // Only update if data has actually changed
- if (title !== currentTitle) {
- currentTitle = title;
- titleElement.classList.add("updating");
- titleElement.textContent = title || "Unknown Title";
- trackChanged = true;
- setTimeout(() => {
- titleElement.classList.remove("updating");
- }, 400);
+ return trimText(element.textContent);
}
- if (artist !== currentArtist) {
- currentArtist = artist;
- artistElement.classList.add("updating");
- artistElement.textContent = artist || "Unknown Artist";
- trackChanged = true;
- setTimeout(() => {
- artistElement.classList.remove("updating");
- }, 400);
+ function trimText(value) {
+ return String(value || "").replace(/^\s+|\s+$/g, "");
}
- // Return whether track changed (to trigger image update)
- return trackChanged;
-}
+ 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;
+ }
-/**
- * Updates both the album art and background image with cache-busting timestamp
- * Only called when track actually changes
- */
-function updateAlbumArt() {
- const albumArt = document.querySelector(".album-art");
- const backgroundImage = document.querySelector(".background-image");
- const timestamp = new Date().getTime();
- const imageUrl = `${IMAGE_FILENAME}?v=${timestamp}`;
-
- albumArt.src = imageUrl;
- backgroundImage.src = imageUrl;
-}
-
-/**
- * Main refresh function - fetches data and updates if changed
- */
-async function refresh() {
- const data = await fetchSongData();
- if (data) {
- const trackChanged = updateSongDisplay(data.artist, data.title);
- // Only update images when track actually changes
- if (trackChanged) {
- updateAlbumArt();
+ 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);
}
}
-}
-///////////////
-// INITIALIZE //
-///////////////
+ function updateSongDisplay(data) {
+ var titleElement = document.querySelector(".song-title");
+ var artistElement = document.querySelector(".song-artist");
+ var trackChanged = false;
+ var nextTitle;
+ var nextArtist;
-function init() {
- // Load initial data immediately
- refresh();
+ if (!titleElement || !artistElement || !data) {
+ return false;
+ }
- // Set up refresh interval based on URL parameter or default
- refreshInterval = setInterval(refresh, refreshRate * 1000);
+ nextTitle = trimText(data.title || "");
+ nextArtist = trimText(data.artist || "");
- console.log(`Radio DJ widget initialized - refreshing every ${refreshRate}s`);
-}
+ if (nextTitle !== currentTitle) {
+ currentTitle = nextTitle;
+ titleElement.classList.add("updating");
+ titleElement.textContent = currentTitle || "Unknown Title";
+ trackChanged = true;
+ setTimeout(function () {
+ titleElement.classList.remove("updating");
+ }, 400);
+ }
-// Start when DOM is ready
-if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", init);
-} else {
- init();
-}
+ 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();
+ }
+})();
diff --git a/RadioDJViewer/style.css b/RadioDJViewer/style.css
index ecfa75f..e72cd3d 100644
--- a/RadioDJViewer/style.css
+++ b/RadioDJViewer/style.css
@@ -127,6 +127,21 @@ body {
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 {
@@ -141,3 +156,22 @@ body {
.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;
+}