2 Commits
Author SHA1 Message Date
minster586 7b67c03270 Added in Windows toast notifications 2026-09-03 00:20:33 -04:00
minster586 34c80bda7b Set Time Clock 2026-07-01 22:37:18 -04:00
12 changed files with 1023 additions and 272 deletions
+84 -45
View File
@@ -43,6 +43,8 @@
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.groupBox1 = new System.Windows.Forms.GroupBox(); this.groupBox1 = new System.Windows.Forms.GroupBox();
this.label8 = new System.Windows.Forms.Label();
this.label7 = new System.Windows.Forms.Label();
this.label6 = new System.Windows.Forms.Label(); this.label6 = new System.Windows.Forms.Label();
this.label5 = new System.Windows.Forms.Label(); this.label5 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label(); this.label4 = new System.Windows.Forms.Label();
@@ -56,8 +58,9 @@
this.profile_drop_box = new System.Windows.Forms.ComboBox(); this.profile_drop_box = new System.Windows.Forms.ComboBox();
this.profile_label = new System.Windows.Forms.Label(); this.profile_label = new System.Windows.Forms.Label();
this.load_profile_button = new System.Windows.Forms.Button(); this.load_profile_button = new System.Windows.Forms.Button();
this.label7 = new System.Windows.Forms.Label(); this.time_label = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label(); this.label9 = new System.Windows.Forms.Label();
this.time_data_label = new System.Windows.Forms.Label();
this.statusStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout();
this.menuStrip2.SuspendLayout(); this.menuStrip2.SuspendLayout();
this.groupBox1.SuspendLayout(); this.groupBox1.SuspendLayout();
@@ -71,9 +74,9 @@
this.toolStripStatusLabel2, this.toolStripStatusLabel2,
this.toolStripStatusLabel4, this.toolStripStatusLabel4,
this.toolStripStatusLabel3}); 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.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(612, 25); this.statusStrip1.Size = new System.Drawing.Size(612, 26);
this.statusStrip1.TabIndex = 0; this.statusStrip1.TabIndex = 0;
this.statusStrip1.Text = "statusStrip1"; this.statusStrip1.Text = "statusStrip1";
// //
@@ -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.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
this.toolStripStatusLabel1.Size = new System.Drawing.Size(57, 20); this.toolStripStatusLabel1.Size = new System.Drawing.Size(64, 21);
this.toolStripStatusLabel1.Text = "Status:"; this.toolStripStatusLabel1.Text = "Status:";
// //
// toolStripStatusLabel2 // toolStripStatusLabel2
// //
this.toolStripStatusLabel2.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.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 // toolStripStatusLabel4
// //
this.toolStripStatusLabel4.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.toolStripStatusLabel4.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.toolStripStatusLabel4.Name = "toolStripStatusLabel4"; this.toolStripStatusLabel4.Name = "toolStripStatusLabel4";
this.toolStripStatusLabel4.Size = new System.Drawing.Size(262, 20); this.toolStripStatusLabel4.Size = new System.Drawing.Size(260, 21);
this.toolStripStatusLabel4.Spring = true; this.toolStripStatusLabel4.Spring = true;
this.toolStripStatusLabel4.Text = "Profile:"; this.toolStripStatusLabel4.Text = "Profile:";
this.toolStripStatusLabel4.Click += new System.EventHandler(this.toolStripStatusLabel4_Click); this.toolStripStatusLabel4.Click += new System.EventHandler(this.toolStripStatusLabel4_Click);
@@ -105,7 +107,7 @@
// toolStripStatusLabel3 // toolStripStatusLabel3
// //
this.toolStripStatusLabel3.Name = "toolStripStatusLabel3"; this.toolStripStatusLabel3.Name = "toolStripStatusLabel3";
this.toolStripStatusLabel3.Size = new System.Drawing.Size(262, 20); this.toolStripStatusLabel3.Size = new System.Drawing.Size(260, 21);
this.toolStripStatusLabel3.Spring = true; this.toolStripStatusLabel3.Spring = true;
this.toolStripStatusLabel3.Text = "Song Changed Updateing"; this.toolStripStatusLabel3.Text = "Song Changed Updateing";
this.toolStripStatusLabel3.TextAlign = System.Drawing.ContentAlignment.MiddleRight; this.toolStripStatusLabel3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
@@ -117,7 +119,7 @@
this.aboutToolStripMenuItem}); this.aboutToolStripMenuItem});
this.menuStrip2.Location = new System.Drawing.Point(0, 0); this.menuStrip2.Location = new System.Drawing.Point(0, 0);
this.menuStrip2.Name = "menuStrip2"; this.menuStrip2.Name = "menuStrip2";
this.menuStrip2.Size = new System.Drawing.Size(612, 24); this.menuStrip2.Size = new System.Drawing.Size(612, 25);
this.menuStrip2.TabIndex = 2; this.menuStrip2.TabIndex = 2;
this.menuStrip2.Text = "menuStrip2"; this.menuStrip2.Text = "menuStrip2";
// //
@@ -128,7 +130,7 @@
this.loggingToolStripMenuItem, this.loggingToolStripMenuItem,
this.exitToolStripMenuItem}); this.exitToolStripMenuItem});
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); this.fileToolStripMenuItem.Size = new System.Drawing.Size(41, 21);
this.fileToolStripMenuItem.Text = "File"; this.fileToolStripMenuItem.Text = "File";
// //
// restAPISettingToolStripMenuItem // restAPISettingToolStripMenuItem
@@ -151,13 +153,13 @@
// onToolStripMenuItem // onToolStripMenuItem
// //
this.onToolStripMenuItem.Name = "onToolStripMenuItem"; this.onToolStripMenuItem.Name = "onToolStripMenuItem";
this.onToolStripMenuItem.Size = new System.Drawing.Size(91, 22); this.onToolStripMenuItem.Size = new System.Drawing.Size(98, 22);
this.onToolStripMenuItem.Text = "On"; this.onToolStripMenuItem.Text = "On";
// //
// offToolStripMenuItem // offToolStripMenuItem
// //
this.offToolStripMenuItem.Name = "offToolStripMenuItem"; this.offToolStripMenuItem.Name = "offToolStripMenuItem";
this.offToolStripMenuItem.Size = new System.Drawing.Size(91, 22); this.offToolStripMenuItem.Size = new System.Drawing.Size(98, 22);
this.offToolStripMenuItem.Text = "Off"; this.offToolStripMenuItem.Text = "Off";
// //
// exitToolStripMenuItem // exitToolStripMenuItem
@@ -170,12 +172,15 @@
// aboutToolStripMenuItem // aboutToolStripMenuItem
// //
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(52, 20); this.aboutToolStripMenuItem.Size = new System.Drawing.Size(52, 21);
this.aboutToolStripMenuItem.Text = "About"; this.aboutToolStripMenuItem.Text = "About";
this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
// //
// groupBox1 // groupBox1
// //
this.groupBox1.Controls.Add(this.time_data_label);
this.groupBox1.Controls.Add(this.label9);
this.groupBox1.Controls.Add(this.time_label);
this.groupBox1.Controls.Add(this.label8); this.groupBox1.Controls.Add(this.label8);
this.groupBox1.Controls.Add(this.label7); this.groupBox1.Controls.Add(this.label7);
this.groupBox1.Controls.Add(this.label6); 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.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.groupBox1.Location = new System.Drawing.Point(199, 37); this.groupBox1.Location = new System.Drawing.Point(199, 37);
this.groupBox1.Name = "groupBox1"; this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(390, 173); this.groupBox1.Size = new System.Drawing.Size(390, 205);
this.groupBox1.TabIndex = 4; this.groupBox1.TabIndex = 4;
this.groupBox1.TabStop = false; this.groupBox1.TabStop = false;
this.groupBox1.Text = "Song Info"; this.groupBox1.Text = "Song Info";
// //
// label8
//
this.label8.AutoSize = true;
this.label8.Location = new System.Drawing.Point(20, 103);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(357, 16);
this.label8.TabIndex = 7;
this.label8.Text = "――――――――――――――――――――――――――――――――――――――――――――――――――";
this.label8.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
//
// label7
//
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(19, 61);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(357, 16);
this.label7.TabIndex = 6;
this.label7.Text = "――――――――――――――――――――――――――――――――――――――――――――――――――";
//
// label6 // label6
// //
this.label6.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 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.Name = "label6";
this.label6.Size = new System.Drawing.Size(266, 17); this.label6.Size = new System.Drawing.Size(266, 17);
this.label6.TabIndex = 5; this.label6.TabIndex = 5;
@@ -205,7 +229,7 @@
// label5 // label5
// //
this.label5.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 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.Name = "label5";
this.label5.Size = new System.Drawing.Size(266, 17); this.label5.Size = new System.Drawing.Size(266, 17);
this.label5.TabIndex = 4; this.label5.TabIndex = 4;
@@ -226,7 +250,7 @@
// //
this.label3.AutoSize = true; 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.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.Name = "label3";
this.label3.Size = new System.Drawing.Size(76, 24); this.label3.Size = new System.Drawing.Size(76, 24);
this.label3.TabIndex = 2; this.label3.TabIndex = 2;
@@ -236,7 +260,7 @@
// //
this.label2.AutoSize = true; 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.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.Name = "label2";
this.label2.Size = new System.Drawing.Size(62, 24); this.label2.Size = new System.Drawing.Size(62, 24);
this.label2.TabIndex = 1; this.label2.TabIndex = 1;
@@ -254,7 +278,7 @@
// //
// button1 // 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.Name = "button1";
this.button1.Size = new System.Drawing.Size(99, 42); this.button1.Size = new System.Drawing.Size(99, 42);
this.button1.TabIndex = 5; this.button1.TabIndex = 5;
@@ -264,7 +288,7 @@
// //
// button2 // 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.Name = "button2";
this.button2.Size = new System.Drawing.Size(99, 42); this.button2.Size = new System.Drawing.Size(99, 42);
this.button2.TabIndex = 6; this.button2.TabIndex = 6;
@@ -275,7 +299,7 @@
// uicolor // uicolor
// //
this.uicolor.Image = global::RadioDJViewer.Properties.Resources.MdiWeatherNight; this.uicolor.Image = global::RadioDJViewer.Properties.Resources.MdiWeatherNight;
this.uicolor.Location = new System.Drawing.Point(572, 276); this.uicolor.Location = new System.Drawing.Point(572, 327);
this.uicolor.Name = "uicolor"; this.uicolor.Name = "uicolor";
this.uicolor.Size = new System.Drawing.Size(28, 23); this.uicolor.Size = new System.Drawing.Size(28, 23);
this.uicolor.TabIndex = 7; this.uicolor.TabIndex = 7;
@@ -283,7 +307,7 @@
// //
// pictureBox1 // pictureBox1
// //
this.pictureBox1.Location = new System.Drawing.Point(13, 37); this.pictureBox1.Location = new System.Drawing.Point(13, 69);
this.pictureBox1.Name = "pictureBox1"; this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(180, 173); this.pictureBox1.Size = new System.Drawing.Size(180, 173);
this.pictureBox1.TabIndex = 3; this.pictureBox1.TabIndex = 3;
@@ -292,7 +316,7 @@
// profile_drop_box // profile_drop_box
// //
this.profile_drop_box.FormattingEnabled = true; 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.Name = "profile_drop_box";
this.profile_drop_box.Size = new System.Drawing.Size(217, 21); this.profile_drop_box.Size = new System.Drawing.Size(217, 21);
this.profile_drop_box.TabIndex = 8; this.profile_drop_box.TabIndex = 8;
@@ -300,7 +324,7 @@
// profile_label // profile_label
// //
this.profile_label.AutoSize = true; 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.Name = "profile_label";
this.profile_label.Size = new System.Drawing.Size(44, 13); this.profile_label.Size = new System.Drawing.Size(44, 13);
this.profile_label.TabIndex = 9; this.profile_label.TabIndex = 9;
@@ -309,37 +333,49 @@
// //
// load_profile_button // 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.Name = "load_profile_button";
this.load_profile_button.Size = new System.Drawing.Size(75, 23); this.load_profile_button.Size = new System.Drawing.Size(75, 23);
this.load_profile_button.TabIndex = 10; this.load_profile_button.TabIndex = 10;
this.load_profile_button.Text = "Load Profile"; this.load_profile_button.Text = "Load Profile";
this.load_profile_button.UseVisualStyleBackColor = true; this.load_profile_button.UseVisualStyleBackColor = true;
// //
// label7 // time_label
// //
this.label7.AutoSize = true; this.time_label.AutoSize = true;
this.label7.Location = new System.Drawing.Point(19, 61); 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.label7.Name = "label7"; this.time_label.Location = new System.Drawing.Point(20, 157);
this.label7.Size = new System.Drawing.Size(357, 16); this.time_label.Name = "time_label";
this.label7.TabIndex = 6; this.time_label.Size = new System.Drawing.Size(63, 24);
this.label7.Text = "――――――――――――――――――――――――――――――――――――――――――――――――――"; 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.label9.AutoSize = true;
this.label8.Location = new System.Drawing.Point(24, 118); this.label9.Location = new System.Drawing.Point(17, 144);
this.label8.Name = "label8"; this.label9.Name = "label9";
this.label8.Size = new System.Drawing.Size(357, 16); this.label9.Size = new System.Drawing.Size(357, 16);
this.label8.TabIndex = 7; this.label9.TabIndex = 9;
this.label8.Text = "――――――――――――――――――――――――――――――――――――――――――――――――――"; this.label9.Text = "――――――――――――――――――――――――――――――――――――――――――――――――――";
this.label8.TextAlign = System.Drawing.ContentAlignment.BottomCenter; this.label9.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
//
// time_data_label
//
this.time_data_label.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.time_data_label.Location = new System.Drawing.Point(97, 160);
this.time_data_label.Name = "time_data_label";
this.time_data_label.Size = new System.Drawing.Size(266, 17);
this.time_data_label.TabIndex = 10;
this.time_data_label.Text = "No Data";
this.time_data_label.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
// //
// Main // Main
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(612, 339); this.ClientSize = new System.Drawing.Size(612, 388);
this.Controls.Add(this.load_profile_button); this.Controls.Add(this.load_profile_button);
this.Controls.Add(this.profile_label); this.Controls.Add(this.profile_label);
this.Controls.Add(this.profile_drop_box); this.Controls.Add(this.profile_drop_box);
@@ -396,6 +432,9 @@
private System.Windows.Forms.Button load_profile_button; private System.Windows.Forms.Button load_profile_button;
private System.Windows.Forms.Label label7; private System.Windows.Forms.Label label7;
private System.Windows.Forms.Label label8; 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;
} }
} }
+519 -36
View File
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Data; using System.Data;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -11,12 +13,22 @@ using System.Xml;
using System.Threading; using System.Threading;
using System.IO; using System.IO;
using System.Resources; using System.Resources;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security;
namespace RadioDJViewer namespace RadioDJViewer
{ {
public partial class Main : Form public partial class Main : Form
{ {
private string _currentTrackKey = string.Empty; private string _currentTrackKey = string.Empty;
private string _playbackTrackKey = string.Empty;
private double _playbackElapsedSeconds = 0;
private double _playbackDurationSeconds = 0;
private bool _playbackPaused = true;
private DateTime _playbackBaseUtc = DateTime.UtcNow;
private string selectedFolderPath = string.Empty; private string selectedFolderPath = string.Empty;
private string outputFolderPath = string.Empty; private string outputFolderPath = string.Empty;
private bool isConnected = false; private bool isConnected = false;
@@ -31,6 +43,7 @@ namespace RadioDJViewer
// Timer to poll REST API // Timer to poll REST API
private System.Windows.Forms.Timer apiTimer; private System.Windows.Forms.Timer apiTimer;
private System.Windows.Forms.Timer playbackTimer;
private string lastApiXml = null; private string lastApiXml = null;
private System.Windows.Forms.Timer marqueeTimerTitle; private System.Windows.Forms.Timer marqueeTimerTitle;
@@ -63,6 +76,9 @@ namespace RadioDJViewer
apiTimer = new System.Windows.Forms.Timer(); apiTimer = new System.Windows.Forms.Timer();
apiTimer.Interval = 3000; // 3 seconds apiTimer.Interval = 3000; // 3 seconds
apiTimer.Tick += ApiTimer_Tick; apiTimer.Tick += ApiTimer_Tick;
playbackTimer = new System.Windows.Forms.Timer();
playbackTimer.Interval = 250;
playbackTimer.Tick += PlaybackTimer_Tick;
// Marquee timers for each label // Marquee timers for each label
marqueeTimerTitle = new System.Windows.Forms.Timer(); marqueeTimerTitle = new System.Windows.Forms.Timer();
marqueeTimerTitle.Interval = marqueeScrollSpeed; marqueeTimerTitle.Interval = marqueeScrollSpeed;
@@ -275,22 +291,40 @@ namespace RadioDJViewer
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
var xml = await response.Content.ReadAsStringAsync(); var xml = await response.Content.ReadAsStringAsync();
if (xml != lastApiXml) lastApiXml = xml;
ParseAndDisplaySongInfo(xml);
try
{ {
lastApiXml = xml; var newKey = $"{marqueeTextArtist}|{marqueeTextTitle}";
ParseAndDisplaySongInfo(xml); if (!string.Equals(newKey, _currentTrackKey, StringComparison.Ordinal))
// Build a key from Artist and Title and only show change if different
try
{ {
var newKey = $"{marqueeTextArtist}|{marqueeTextTitle}"; _currentTrackKey = newKey;
if (!string.Equals(newKey, _currentTrackKey, StringComparison.Ordinal)) ShowTemporaryStatus("Song Change Detected", 2000);
bool toastEnabled = IsToastEnabledForActiveProfile();
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Track changed. Enabled={toastEnabled}, Profile={loadedProfile?.Name ?? "(null)"}");
#endif
if (toastEnabled)
{ {
_currentTrackKey = newKey; try
ShowTemporaryStatus("Song Change Detected", 2000); {
string outputImagePath = GetProfileOutputImagePath();
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Trigger with title='{marqueeTextTitle}', artist='{marqueeTextArtist}', imagePath='{outputImagePath}'");
#endif
ShowTrackChangeToast(marqueeTextTitle, marqueeTextArtist, outputImagePath);
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Trigger error: {ex}");
MessageBox.Show($"Toast trigger failed: {ex.Message}", "Toast Debug", MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
}
} }
} }
catch { }
} }
catch { }
} }
} }
catch { } catch { }
@@ -332,6 +366,16 @@ namespace RadioDJViewer
// Disconnect // Disconnect
isConnected = false; isConnected = false;
apiTimer.Stop(); apiTimer.Stop();
playbackTimer.Stop();
_playbackElapsedSeconds = 0;
_playbackDurationSeconds = 0;
_playbackPaused = true;
_playbackTrackKey = string.Empty;
time_data_label.Text = "No Data";
WidgetServer.CachedElapsed = "0";
WidgetServer.CachedDuration = "0";
WidgetServer.CachedTime = "No Data";
WidgetServer.CachedPaused = true;
// Stop widget server when disconnecting // Stop widget server when disconnecting
try try
{ {
@@ -427,6 +471,7 @@ namespace RadioDJViewer
{ {
var resized = new Bitmap(fallbackImg, pictureBox1.Size); var resized = new Bitmap(fallbackImg, pictureBox1.Size);
pictureBox1.Image = resized; pictureBox1.Image = resized;
currentSongImagePath = defaultImagePath;
// Save fallback image to output folder as PNG // Save fallback image to output folder as PNG
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName)) if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
{ {
@@ -437,6 +482,7 @@ namespace RadioDJViewer
else else
{ {
pictureBox1.Image = null; pictureBox1.Image = null;
currentSongImagePath = defaultImagePath;
} }
return; return;
} }
@@ -446,6 +492,7 @@ namespace RadioDJViewer
{ {
var resized = new Bitmap(img, pictureBox1.Size); var resized = new Bitmap(img, pictureBox1.Size);
pictureBox1.Image = resized; pictureBox1.Image = resized;
currentSongImagePath = imagePath;
// Save to output folder as PNG // Save to output folder as PNG
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName)) if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
{ {
@@ -462,6 +509,7 @@ namespace RadioDJViewer
{ {
var resized = new Bitmap(fallbackImg, pictureBox1.Size); var resized = new Bitmap(fallbackImg, pictureBox1.Size);
pictureBox1.Image = resized; pictureBox1.Image = resized;
currentSongImagePath = defaultImagePath;
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName)) if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
{ {
string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png")); string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png"));
@@ -471,6 +519,7 @@ namespace RadioDJViewer
else else
{ {
pictureBox1.Image = null; pictureBox1.Image = null;
currentSongImagePath = defaultImagePath;
} }
} }
} }
@@ -492,6 +541,82 @@ namespace RadioDJViewer
marqueeTimerAlbum.Stop(); marqueeTimerAlbum.Stop();
} }
private void PlaybackTimer_Tick(object sender, EventArgs e)
{
UpdatePlaybackDisplay();
}
private void UpdatePlaybackState(string trackKey, string elapsedText, string durationText)
{
double elapsedSeconds;
double durationSeconds;
if (!TryParseSeconds(elapsedText, out elapsedSeconds) || !TryParseSeconds(durationText, out durationSeconds) || durationSeconds <= 0)
{
playbackTimer.Stop();
_playbackTrackKey = trackKey ?? string.Empty;
_playbackElapsedSeconds = 0;
_playbackDurationSeconds = 0;
_playbackPaused = true;
time_data_label.Text = "No Data";
WidgetServer.CachedElapsed = "0";
WidgetServer.CachedDuration = "0";
WidgetServer.CachedTime = "No Data";
WidgetServer.CachedPaused = true;
return;
}
bool sameTrack = string.Equals(trackKey, _playbackTrackKey, StringComparison.Ordinal);
bool sameElapsed = Math.Abs(elapsedSeconds - _playbackElapsedSeconds) < 0.01;
bool sameDuration = Math.Abs(durationSeconds - _playbackDurationSeconds) < 0.01;
_playbackTrackKey = trackKey ?? string.Empty;
_playbackElapsedSeconds = elapsedSeconds;
_playbackDurationSeconds = durationSeconds;
_playbackBaseUtc = DateTime.UtcNow;
_playbackPaused = sameTrack && sameElapsed && sameDuration;
if (_playbackPaused)
playbackTimer.Stop();
else
playbackTimer.Start();
UpdatePlaybackDisplay();
}
private void UpdatePlaybackDisplay()
{
double currentElapsed = GetCurrentElapsedSeconds();
string timeDisplay = FormatTimeDisplay(currentElapsed, _playbackDurationSeconds);
time_data_label.Text = timeDisplay;
WidgetServer.CachedElapsed = currentElapsed.ToString("0.###", CultureInfo.InvariantCulture);
WidgetServer.CachedDuration = _playbackDurationSeconds.ToString("0.###", CultureInfo.InvariantCulture);
WidgetServer.CachedTime = timeDisplay;
WidgetServer.CachedPaused = _playbackPaused;
}
private double GetCurrentElapsedSeconds()
{
if (_playbackDurationSeconds <= 0)
return 0;
double currentElapsed = _playbackElapsedSeconds;
if (!_playbackPaused)
currentElapsed += (DateTime.UtcNow - _playbackBaseUtc).TotalSeconds;
if (currentElapsed < 0)
currentElapsed = 0;
if (currentElapsed > _playbackDurationSeconds)
currentElapsed = _playbackDurationSeconds;
return currentElapsed;
}
private bool TryParseSeconds(string value, out double seconds)
{
return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out seconds)
|| double.TryParse(value, NumberStyles.Float, CultureInfo.CurrentCulture, out seconds);
}
private void ParseAndDisplaySongInfo(string xml) private void ParseAndDisplaySongInfo(string xml)
{ {
try try
@@ -503,12 +628,17 @@ namespace RadioDJViewer
string artist = doc.SelectSingleNode("//*[translate(local-name(), 'ARTIST', 'artist')='artist']")?.InnerText; string artist = doc.SelectSingleNode("//*[translate(local-name(), 'ARTIST', 'artist')='artist']")?.InnerText;
string album = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUM', 'album')='album']")?.InnerText; string album = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUM', 'album')='album']")?.InnerText;
string albumArt = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUMART', 'albumart')='albumart']")?.InnerText; string albumArt = doc.SelectSingleNode("//*[translate(local-name(), 'ALBUMART', 'albumart')='albumart']")?.InnerText;
// Parse elapsed and duration time (case-insensitive, floating-point seconds)
string elapsed = doc.SelectSingleNode("//*[translate(local-name(), 'ELAPSED', 'elapsed')='elapsed']")?.InnerText;
string duration = doc.SelectSingleNode("//*[translate(local-name(), 'DURATION', 'duration')='duration']")?.InnerText;
marqueeTextTitle = !string.IsNullOrWhiteSpace(title) ? title : "No title"; marqueeTextTitle = !string.IsNullOrWhiteSpace(title) ? title : "No title";
marqueeTextArtist = !string.IsNullOrWhiteSpace(artist) ? artist : "No artist"; marqueeTextArtist = !string.IsNullOrWhiteSpace(artist) ? artist : "No artist";
marqueeTextAlbum = !string.IsNullOrWhiteSpace(album) ? album : "No album"; marqueeTextAlbum = !string.IsNullOrWhiteSpace(album) ? album : "No album";
StartMarqueeTimers(); StartMarqueeTimers();
UpdatePlaybackState($"{marqueeTextArtist}|{marqueeTextTitle}", elapsed, duration);
// Write to output files // Write to output files
if (!string.IsNullOrEmpty(outputFolderPath)) if (!string.IsNullOrEmpty(outputFolderPath))
{ {
@@ -522,6 +652,7 @@ namespace RadioDJViewer
{ {
WidgetServer.CachedArtist = marqueeTextArtist ?? string.Empty; WidgetServer.CachedArtist = marqueeTextArtist ?? string.Empty;
WidgetServer.CachedTitle = marqueeTextTitle ?? string.Empty; WidgetServer.CachedTitle = marqueeTextTitle ?? string.Empty;
byte[] imgBytes = null; byte[] imgBytes = null;
// Prefer album art from main images folder // Prefer album art from main images folder
if (!string.IsNullOrEmpty(albumArt) && !string.IsNullOrEmpty(mainImagesFolderPath)) if (!string.IsNullOrEmpty(albumArt) && !string.IsNullOrEmpty(mainImagesFolderPath))
@@ -576,32 +707,38 @@ namespace RadioDJViewer
marqueeTextArtist = "No artist"; marqueeTextArtist = "No artist";
marqueeTextAlbum = "No album"; marqueeTextAlbum = "No album";
StartMarqueeTimers(); StartMarqueeTimers();
playbackTimer.Stop();
_playbackElapsedSeconds = 0;
_playbackDurationSeconds = 0;
_playbackPaused = true;
time_data_label.Text = "No Data";
WidgetServer.CachedElapsed = "0";
WidgetServer.CachedDuration = "0";
WidgetServer.CachedTime = "No Data";
WidgetServer.CachedPaused = true;
pictureBox1.Image = null; pictureBox1.Image = null;
} }
} }
private void UpdateStatusBar() private void UpdateStatusBar()
{ {
// Update color box based on connection states
try try
{ {
if (!isConnected) if (!isConnected)
{ {
toolStripStatusLabel2.BackColor = Color.Red; // disconnected/error toolStripStatusLabel2.BackColor = Color.Red;
} }
else else
{ {
if (isConnected && widgetRunning) if (isConnected && widgetRunning)
toolStripStatusLabel2.BackColor = Color.Blue; // both running toolStripStatusLabel2.BackColor = Color.Blue;
else else
toolStripStatusLabel2.BackColor = Color.Green; // API ok, web off toolStripStatusLabel2.BackColor = Color.Green;
} }
} }
catch { } catch { }
// Profile
toolStripStatusLabel4.Text = $"Profile: {currentProfile}"; toolStripStatusLabel4.Text = $"Profile: {currentProfile}";
// If there's no active temporary message, ensure label3 shows default
if (statusCts == null) if (statusCts == null)
{ {
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected"; toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected";
@@ -610,7 +747,6 @@ namespace RadioDJViewer
private void ShowTemporaryStatus(string message, int milliseconds) private void ShowTemporaryStatus(string message, int milliseconds)
{ {
// Cancel any previous temporary status
try try
{ {
statusCts?.Cancel(); statusCts?.Cancel();
@@ -620,7 +756,6 @@ namespace RadioDJViewer
if (milliseconds <= 0) if (milliseconds <= 0)
{ {
// Persistent message until changed - keep a non-null token so UpdateStatusBar doesn't overwrite
var persistent = new CancellationTokenSource(); var persistent = new CancellationTokenSource();
statusCts = persistent; statusCts = persistent;
toolStripStatusLabel3.Text = message; toolStripStatusLabel3.Text = message;
@@ -638,7 +773,6 @@ namespace RadioDJViewer
await Task.Delay(milliseconds, cts.Token); await Task.Delay(milliseconds, cts.Token);
if (!cts.IsCancellationRequested) if (!cts.IsCancellationRequested)
{ {
// clear temporary message
statusCts = null; statusCts = null;
this.BeginInvoke((Action)(() => { this.BeginInvoke((Action)(() => {
toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected"; toolStripStatusLabel3.Text = isConnected ? "Connected to API" : "Disconnected";
@@ -658,12 +792,13 @@ namespace RadioDJViewer
private void StartWidgetServerIfEnabled() private void StartWidgetServerIfEnabled()
{ {
// Called after API connection is confirmed
if (loadedProfile == null || !loadedProfile.WebServerEnabled) if (loadedProfile == null || !loadedProfile.WebServerEnabled)
return; return;
try try
{ {
WidgetServer.CachedClockVisibility = loadedProfile.ClockVisibilityEnabled;
if (widgetServer == null) if (widgetServer == null)
{ {
int port = loadedProfile.WebServerPort > 0 ? loadedProfile.WebServerPort : 8080; int port = loadedProfile.WebServerPort > 0 ? loadedProfile.WebServerPort : 8080;
@@ -678,16 +813,14 @@ namespace RadioDJViewer
{ {
widgetRunning = false; widgetRunning = false;
UpdateStatusBar(); UpdateStatusBar();
// Show error message
ShowTemporaryStatus($"Web server error: {ex.Message}", 5000); ShowTemporaryStatus($"Web server error: {ex.Message}", 5000);
} }
} }
private void restAPISettingToolStripMenuItem_Click(object sender, EventArgs e) private void restAPISettingToolStripMenuItem_Click(object sender, EventArgs e)
{ {
// Open settings/profile dialog
var settingsForm = new Form1(); var settingsForm = new Form1();
settingsForm.ApplyTheme(isDarkMode); // Pass current color mode settingsForm.ApplyTheme(isDarkMode);
settingsForm.ShowDialog(); settingsForm.ShowDialog();
} }
@@ -708,32 +841,28 @@ namespace RadioDJViewer
private void toolStripStatusLabel4_Click(object sender, EventArgs e) private void toolStripStatusLabel4_Click(object sender, EventArgs e)
{ {
// Profile click handler (to be implemented)
} }
public void LoadProfile(Profile profile) public void LoadProfile(Profile profile)
{ {
// Update main form fields from profile
currentProfile = profile.Name; currentProfile = profile.Name;
selectedFolderPath = profile.OutputFolder; selectedFolderPath = profile.OutputFolder;
outputFolderPath = profile.OutputFolder; outputFolderPath = profile.OutputFolder;
mainImagesFolderPath = profile.MainImagesFolder; // Update new field mainImagesFolderPath = profile.MainImagesFolder;
loadedProfile = profile; // Store loaded profile for REST API connection loadedProfile = profile;
// Update polling rate when profile is loaded
int pollingRate = profile.PollingRateSeconds > 0 ? profile.PollingRateSeconds : 3; int pollingRate = profile.PollingRateSeconds > 0 ? profile.PollingRateSeconds : 3;
apiTimer.Interval = pollingRate * 1000; apiTimer.Interval = pollingRate * 1000;
SetMarqueeScrollSpeed(profile.MarqueeScrollSpeed ?? "Medium"); SetMarqueeScrollSpeed(profile.MarqueeScrollSpeed ?? "Medium");
SetMarqueePauseTime(profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime : 6); SetMarqueePauseTime(profile.MarqueePauseTime > 0 ? profile.MarqueePauseTime : 6);
SetMarqueeSeparator(profile.MarqueeSeparator ?? " | "); SetMarqueeSeparator(profile.MarqueeSeparator ?? " | ");
// Immediately apply marquee settings to timers
if (pauseTimerTitle != null) pauseTimerTitle.Interval = marqueePauseTime; if (pauseTimerTitle != null) pauseTimerTitle.Interval = marqueePauseTime;
if (pauseTimerArtist != null) pauseTimerArtist.Interval = marqueePauseTime; if (pauseTimerArtist != null) pauseTimerArtist.Interval = marqueePauseTime;
if (pauseTimerAlbum != null) pauseTimerAlbum.Interval = marqueePauseTime; if (pauseTimerAlbum != null) pauseTimerAlbum.Interval = marqueePauseTime;
marqueeTimerTitle.Interval = marqueeScrollSpeed; marqueeTimerTitle.Interval = marqueeScrollSpeed;
marqueeTimerArtist.Interval = marqueeScrollSpeed; marqueeTimerArtist.Interval = marqueeScrollSpeed;
marqueeTimerAlbum.Interval = marqueeScrollSpeed; marqueeTimerAlbum.Interval = marqueeScrollSpeed;
WidgetServer.CachedClockVisibility = profile.ClockVisibilityEnabled;
UpdateStatusBar(); UpdateStatusBar();
// You can add more logic to update other fields if needed
} }
private void Uicolor_Click(object sender, EventArgs e) private void Uicolor_Click(object sender, EventArgs e)
@@ -759,23 +888,19 @@ namespace RadioDJViewer
private void UpdateTheme() private void UpdateTheme()
{ {
// Set icon
Bitmap icon = isDarkMode ? Properties.Resources.MdiWeatherSunny : Properties.Resources.MdiWeatherNight; Bitmap icon = isDarkMode ? Properties.Resources.MdiWeatherSunny : Properties.Resources.MdiWeatherNight;
// Invert sun icon for visibility in dark mode
if (isDarkMode && icon != null) if (isDarkMode && icon != null)
{ {
icon = InvertIcon(icon); icon = InvertIcon(icon);
} }
// Resize icon to fit button and center it
if (icon != null) if (icon != null)
{ {
int btnW = uicolor.Width; int btnW = uicolor.Width;
int btnH = uicolor.Height; int btnH = uicolor.Height;
var resizedIcon = new Bitmap(icon, btnW - 8, btnH - 8); // Padding for centering var resizedIcon = new Bitmap(icon, btnW - 8, btnH - 8);
uicolor.Image = resizedIcon; uicolor.Image = resizedIcon;
uicolor.ImageAlign = ContentAlignment.MiddleCenter; uicolor.ImageAlign = ContentAlignment.MiddleCenter;
} }
// Set colors
Color backColor = isDarkMode ? Color.FromArgb(32, 32, 32) : SystemColors.Control; Color backColor = isDarkMode ? Color.FromArgb(32, 32, 32) : SystemColors.Control;
Color foreColor = isDarkMode ? Color.White : SystemColors.ControlText; Color foreColor = isDarkMode ? Color.White : SystemColors.ControlText;
this.BackColor = backColor; this.BackColor = backColor;
@@ -798,7 +923,10 @@ namespace RadioDJViewer
private void label7_Click(object sender, EventArgs e) private void label7_Click(object sender, EventArgs e)
{ {
}
private void label9_Click(object sender, EventArgs e)
{
} }
public string GetCurrentProfileName() public string GetCurrentProfileName()
@@ -811,7 +939,6 @@ namespace RadioDJViewer
profile_drop_box.Items.Clear(); profile_drop_box.Items.Clear();
var profileNames = ProfileStorage.GetProfileNames(); var profileNames = ProfileStorage.GetProfileNames();
profile_drop_box.Items.AddRange(profileNames.ToArray()); profile_drop_box.Items.AddRange(profileNames.ToArray());
// Set selected item to current profile if available
string currentProfile = GetCurrentProfileName(); string currentProfile = GetCurrentProfileName();
if (!string.IsNullOrEmpty(currentProfile) && profileNames.Contains(currentProfile)) if (!string.IsNullOrEmpty(currentProfile) && profileNames.Contains(currentProfile))
profile_drop_box.SelectedItem = currentProfile; profile_drop_box.SelectedItem = currentProfile;
@@ -832,5 +959,361 @@ 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}";
}
private bool IsToastEnabledForActiveProfile()
{
return loadedProfile != null && loadedProfile.ToastNotificationsEnabled;
}
private string GetProfileOutputImagePath()
{
if (string.IsNullOrWhiteSpace(outputFolderPath) || string.IsNullOrWhiteSpace(loadedProfile?.OutputImageName))
return string.Empty;
try
{
string imageName = Path.ChangeExtension(loadedProfile.OutputImageName, ".png");
string fullPath = Path.GetFullPath(Path.Combine(outputFolderPath, imageName));
return fullPath;
}
catch
{
return string.Empty;
}
}
private void ShowTrackChangeToast(string title, string artist, string outputImagePath)
{
string safeTitle = string.IsNullOrWhiteSpace(title) ? "No title" : title;
string safeArtist = string.IsNullOrWhiteSpace(artist) ? "No artist" : artist;
DateTime now = DateTime.Now;
string attribution = $"Date: {now:MM/dd/yyyy} | Time: {now:hh:mm tt}";
try
{
ToastNotificationService.ShowTrackChangeToast(safeTitle, safeArtist, attribution, outputImagePath);
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] ShowTrackChangeToast failed: {ex}");
MessageBox.Show($"Toast display failed: {ex.Message}", "Toast Debug", MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
}
}
}
internal static class ToastNotificationService
{
private const string AppId = "RadioDJViewer.App";
private static bool _initialized;
public static void ShowTrackChangeToast(string trackTitle, string artistName, string attributionLine, string outputImagePath)
{
try
{
EnsureInitialized();
string toastXml = BuildToastXml(trackTitle, artistName, attributionLine, outputImagePath);
var xmlDocumentType = Type.GetType("Windows.Data.Xml.Dom.XmlDocument, Windows, ContentType=WindowsRuntime");
var toastType = Type.GetType("Windows.UI.Notifications.ToastNotification, Windows, ContentType=WindowsRuntime");
var managerType = Type.GetType("Windows.UI.Notifications.ToastNotificationManager, Windows, ContentType=WindowsRuntime");
if (xmlDocumentType == null || toastType == null || managerType == null)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Toast] Windows Runtime toast types are unavailable.");
#endif
return;
}
object xmlDocument = Activator.CreateInstance(xmlDocumentType);
MethodInfo loadXmlMethod = xmlDocumentType.GetMethod(
"LoadXml",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { typeof(string) },
null);
if (loadXmlMethod == null)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Toast] LoadXml(string) method not found.");
#endif
return;
}
loadXmlMethod.Invoke(xmlDocument, new object[] { toastXml });
object toast = Activator.CreateInstance(toastType, new[] { xmlDocument });
MethodInfo createNotifierMethod = managerType.GetMethod(
"CreateToastNotifier",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(string) },
null);
if (createNotifierMethod == null)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Toast] CreateToastNotifier(string) method not found.");
#endif
return;
}
object notifier = createNotifierMethod.Invoke(null, new object[] { AppId });
if (notifier == null)
return;
MethodInfo showMethod = notifier.GetType().GetMethod(
"Show",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { toastType },
null);
if (showMethod == null)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Toast] Show(ToastNotification) method not found.");
#endif
return;
}
showMethod.Invoke(notifier, new[] { toast });
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Toast shown for AppId='{AppId}'.");
#endif
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Service error: {ex}");
MessageBox.Show($"Toast service error: {ex.Message}", "Toast Debug", MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
}
}
private static void EnsureInitialized()
{
if (_initialized)
return;
DesktopToastRegistrar.EnsureStartMenuShortcut(AppId);
_initialized = true;
}
private static string BuildToastXml(string trackTitle, string artistName, string attributionLine, string outputImagePath)
{
string safeTitle = SecurityElement.Escape(string.IsNullOrWhiteSpace(trackTitle) ? "No title" : trackTitle);
string safeArtist = SecurityElement.Escape(string.IsNullOrWhiteSpace(artistName) ? "No artist" : artistName);
string safeAttribution = SecurityElement.Escape(attributionLine ?? string.Empty);
string imageUri = string.Empty;
if (!string.IsNullOrWhiteSpace(outputImagePath))
{
try
{
string fullPath = Path.GetFullPath(outputImagePath);
if (File.Exists(fullPath))
{
var localUri = new Uri(fullPath);
imageUri = localUri.AbsoluteUri;
}
#if DEBUG
else
{
System.Diagnostics.Debug.WriteLine($"[Toast] Output image file missing: '{fullPath}'");
}
#endif
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Invalid output image path '{outputImagePath}': {ex.Message}");
#endif
}
}
string safeImage = SecurityElement.Escape(imageUri);
string imageNode = string.IsNullOrWhiteSpace(safeImage)
? string.Empty
: $"<image placement='appLogoOverride' hint-crop='none' src='{safeImage}'/>";
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Image URI used: '{imageUri}'");
#endif
return $"<toast><visual><binding template='ToastGeneric'>{imageNode}<text>{safeTitle}</text><text>{safeArtist}</text><text>{safeAttribution}</text></binding></visual></toast>";
}
}
internal static class DesktopToastRegistrar
{
[ComImport]
[Guid("00021401-0000-0000-C000-000000000046")]
private class CShellLink { }
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
private interface IShellLinkW
{
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cch, out WIN32_FIND_DATAW pfd, uint fFlags);
void GetIDList(out IntPtr ppidl);
void SetIDList(IntPtr pidl);
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cch);
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cch);
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cch);
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
void GetHotkey(out short pwHotkey);
void SetHotkey(short wHotkey);
void GetShowCmd(out int piShowCmd);
void SetShowCmd(int iShowCmd);
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cch, out int piIcon);
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved);
void Resolve(IntPtr hwnd, uint fFlags);
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct WIN32_FIND_DATAW
{
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
private interface IPropertyStore
{
uint GetCount(out uint cProps);
uint GetAt(uint iProp, out PROPERTYKEY pkey);
uint GetValue(ref PROPERTYKEY key, out PROPVARIANT pv);
uint SetValue(ref PROPERTYKEY key, ref PROPVARIANT pv);
uint Commit();
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct PROPERTYKEY
{
public Guid fmtid;
public uint pid;
}
[StructLayout(LayoutKind.Explicit)]
private struct PROPVARIANT
{
[FieldOffset(0)]
public ushort vt;
[FieldOffset(8)]
public IntPtr pointerValue;
public static PROPVARIANT FromString(string value)
{
var pv = new PROPVARIANT();
pv.vt = 31;
pv.pointerValue = Marshal.StringToCoTaskMemUni(value);
return pv;
}
}
private static PROPERTYKEY PKEY_AppUserModel_ID = new PROPERTYKEY
{
fmtid = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"),
pid = 5
};
public static void EnsureStartMenuShortcut(string appId)
{
try
{
string shortcutPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),
"Programs",
"RadioDJ Viewer.lnk");
string exePath = Assembly.GetEntryAssembly()?.Location ?? Application.ExecutablePath;
string workDir = Path.GetDirectoryName(exePath) ?? AppDomain.CurrentDomain.BaseDirectory;
string iconPath = Path.Combine(workDir, "icon.ico");
if (!File.Exists(iconPath))
iconPath = exePath;
var shellLink = (IShellLinkW)new CShellLink();
shellLink.SetPath(exePath);
shellLink.SetWorkingDirectory(workDir);
shellLink.SetIconLocation(iconPath, 0);
var propertyStore = (IPropertyStore)shellLink;
var value = PROPVARIANT.FromString(appId);
propertyStore.SetValue(ref PKEY_AppUserModel_ID, ref value);
propertyStore.Commit();
((IPersistFile)shellLink).Save(shortcutPath, true);
Marshal.FreeCoTaskMem(value.pointerValue);
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Shortcut registered: '{shortcutPath}', AppId='{appId}'");
#endif
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Shortcut registration error: {ex}");
MessageBox.Show($"Toast registration failed: {ex.Message}", "Toast Debug", MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
}
}
} }
} }
+1 -8
View File
@@ -120,17 +120,10 @@
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value> <value>17, 17</value>
</metadata> </metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="toolStripStatusLabel2.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAABGdBTUEAALGPC/xhBQAAAANQTFRFJn8A
Mx1isQAAAAlwSFlzAAAOvAAADrwBlbxySQAAAB9JREFUaN7twTEBAAAAwqD1T20IX6AAAAAAAAAAAD4D
IIAAAT2Y3yoAAAAASUVORK5CYII=
</value>
</data>
<metadata name="menuStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="menuStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>247, 17</value> <value>247, 17</value>
</metadata> </metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value> <value>
AAABAAEAIBgAAAEAIACIDAAAFgAAACgAAAAgAAAAMAAAAAEAIAAAAAAAAAwAAMMOAADDDgAAAAAAAAAA AAABAAEAIBgAAAEAIACIDAAAFgAAACgAAAAgAAAAMAAAAAEAIAAAAAAAAAwAAMMOAADDDgAAAAAAAAAA
+3
View File
@@ -23,6 +23,9 @@ namespace RadioDJViewer
// Web server settings // Web server settings
public bool WebServerEnabled { get; set; } public bool WebServerEnabled { get; set; }
public int WebServerPort { get; set; } public int WebServerPort { get; set; }
// Clock visibility setting
public bool ClockVisibilityEnabled { get; set; }
public bool ToastNotificationsEnabled { get; set; }
} }
public static class ProfileStorage public static class ProfileStorage
+10 -3
View File
@@ -1,19 +1,26 @@
using System; using System;
using System.Collections.Generic; using System.Runtime.InteropServices;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace RadioDJViewer namespace RadioDJViewer
{ {
internal static class Program internal static class Program
{ {
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int SetCurrentProcessExplicitAppUserModelID(string AppID);
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
static void Main() static void Main()
{ {
try
{
SetCurrentProcessExplicitAppUserModelID("RadioDJViewer.App");
}
catch { }
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Main()); Application.Run(new Main());
+2 -2
View File
@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
// Build Number // Build Number
// Revision // Revision
// //
[assembly: AssemblyVersion("1.9.0")] [assembly: AssemblyVersion("1.14.0")]
[assembly: AssemblyFileVersion("1.9.0")] [assembly: AssemblyFileVersion("1.14.0")]
+78 -1
View File
@@ -21,7 +21,12 @@ namespace RadioDJViewer
// In-memory cache for fast serving // In-memory cache for fast serving
public static string CachedArtist = ""; public static string CachedArtist = "";
public static string CachedTitle = ""; 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 byte[] CachedImageBytes = null;
public static bool CachedClockVisibility = false;
private readonly int port; private readonly int port;
private readonly Func<string> getArtist; private readonly Func<string> getArtist;
@@ -112,14 +117,35 @@ namespace RadioDJViewer
var html = htmlTemplate ?? ""; var html = htmlTemplate ?? "";
// Replace placeholders with in-memory cache values // Replace placeholders with in-memory cache values
html = html.Replace("{{ARTIST}}", WebUtility.HtmlEncode(CachedArtist ?? string.Empty)) 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); var buf = Encoding.UTF8.GetBytes(html);
resp.ContentType = "text/html; charset=utf-8"; resp.ContentType = "text/html; charset=utf-8";
resp.ContentLength64 = buf.Length; resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, 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)) else if (path.Equals("/artist.txt", StringComparison.OrdinalIgnoreCase))
{ {
ApplyNoCache(resp);
var buf = Encoding.UTF8.GetBytes(CachedArtist ?? string.Empty); var buf = Encoding.UTF8.GetBytes(CachedArtist ?? string.Empty);
resp.ContentType = "text/plain; charset=utf-8"; resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buf.Length; resp.ContentLength64 = buf.Length;
@@ -127,11 +153,44 @@ namespace RadioDJViewer
} }
else if (path.Equals("/title.txt", StringComparison.OrdinalIgnoreCase)) else if (path.Equals("/title.txt", StringComparison.OrdinalIgnoreCase))
{ {
ApplyNoCache(resp);
var buf = Encoding.UTF8.GetBytes(CachedTitle ?? string.Empty); var buf = Encoding.UTF8.GetBytes(CachedTitle ?? string.Empty);
resp.ContentType = "text/plain; charset=utf-8"; resp.ContentType = "text/plain; charset=utf-8";
resp.ContentLength64 = buf.Length; resp.ContentLength64 = buf.Length;
resp.OutputStream.Write(buf, 0, 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)) else if (path.Equals("/style.css", StringComparison.OrdinalIgnoreCase))
{ {
var buf = Encoding.UTF8.GetBytes(cssText ?? ""); 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) private string GetMimeTypeFromPath(string path)
{ {
var ext = Path.GetExtension(path).ToLowerInvariant(); var ext = Path.GetExtension(path).ToLowerInvariant();
+3 -2
View File
@@ -25,8 +25,9 @@
<!-- Song info (right) --> <!-- Song info (right) -->
<div class="song-info"> <div class="song-info">
<div class="song-title">Loading...</div> <div class="song-title">{{TITLE}}</div>
<div class="song-artist">Loading...</div> <div class="song-artist">{{ARTIST}}</div>
<div class="song-time" id="song-time">{{TIME}}</div>
</div> </div>
</div> </div>
</div> </div>
+125 -60
View File
@@ -56,6 +56,16 @@
this.label7 = new System.Windows.Forms.Label(); this.label7 = new System.Windows.Forms.Label();
this.textBox7 = new System.Windows.Forms.TextBox(); this.textBox7 = new System.Windows.Forms.TextBox();
this.groupBox2 = new System.Windows.Forms.GroupBox(); this.groupBox2 = new System.Windows.Forms.GroupBox();
this.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.label13 = new System.Windows.Forms.Label();
this.comboBoxScrollSpeed = new System.Windows.Forms.ComboBox(); this.comboBoxScrollSpeed = new System.Windows.Forms.ComboBox();
this.textBoxPauseTime = new System.Windows.Forms.TextBox(); this.textBoxPauseTime = new System.Windows.Forms.TextBox();
@@ -64,14 +74,10 @@
this.label9 = new System.Windows.Forms.Label(); this.label9 = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label(); this.label8 = new System.Windows.Forms.Label();
this.label12 = new System.Windows.Forms.Label(); this.label12 = new System.Windows.Forms.Label();
this.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.groupBox1.SuspendLayout(); this.groupBox1.SuspendLayout();
this.groupBox3.SuspendLayout(); this.groupBox3.SuspendLayout();
this.groupBox2.SuspendLayout(); this.groupBox2.SuspendLayout();
this.toast_box_group_box.SuspendLayout();
this.web_server_settings_groupbox.SuspendLayout(); this.web_server_settings_groupbox.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
@@ -104,7 +110,7 @@
// //
// ok_button_master // ok_button_master
// //
this.ok_button_master.Location = new System.Drawing.Point(65, 648); this.ok_button_master.Location = new System.Drawing.Point(54, 729);
this.ok_button_master.Name = "ok_button_master"; this.ok_button_master.Name = "ok_button_master";
this.ok_button_master.Size = new System.Drawing.Size(157, 23); this.ok_button_master.Size = new System.Drawing.Size(157, 23);
this.ok_button_master.TabIndex = 1; this.ok_button_master.TabIndex = 1;
@@ -113,7 +119,7 @@
// //
// button2 // button2
// //
this.button2.Location = new System.Drawing.Point(305, 648); this.button2.Location = new System.Drawing.Point(302, 729);
this.button2.Name = "button2"; this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(157, 23); this.button2.Size = new System.Drawing.Size(157, 23);
this.button2.TabIndex = 2; this.button2.TabIndex = 2;
@@ -187,7 +193,7 @@
// //
// button3 // 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.Name = "button3";
this.button3.Size = new System.Drawing.Size(120, 30); this.button3.Size = new System.Drawing.Size(120, 30);
this.button3.TabIndex = 8; this.button3.TabIndex = 8;
@@ -196,7 +202,7 @@
// //
// button4 // 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.Name = "button4";
this.button4.Size = new System.Drawing.Size(120, 30); this.button4.Size = new System.Drawing.Size(120, 30);
this.button4.TabIndex = 9; this.button4.TabIndex = 9;
@@ -209,7 +215,7 @@
this.groupBox3.Controls.Add(this.labelOutputFolderPath); this.groupBox3.Controls.Add(this.labelOutputFolderPath);
this.groupBox3.Controls.Add(this.textBox5); this.groupBox3.Controls.Add(this.textBox5);
this.groupBox3.Controls.Add(this.label10); this.groupBox3.Controls.Add(this.label10);
this.groupBox3.Location = new System.Drawing.Point(31, 370); this.groupBox3.Location = new System.Drawing.Point(22, 413);
this.groupBox3.Name = "groupBox3"; this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(482, 115); this.groupBox3.Size = new System.Drawing.Size(482, 115);
this.groupBox3.TabIndex = 12; this.groupBox3.TabIndex = 12;
@@ -302,6 +308,7 @@
// //
// groupBox2 // groupBox2
// //
this.groupBox2.Controls.Add(this.toast_box_group_box);
this.groupBox2.Controls.Add(this.web_server_settings_groupbox); this.groupBox2.Controls.Add(this.web_server_settings_groupbox);
this.groupBox2.Controls.Add(this.label13); this.groupBox2.Controls.Add(this.label13);
this.groupBox2.Controls.Add(this.comboBoxScrollSpeed); this.groupBox2.Controls.Add(this.comboBoxScrollSpeed);
@@ -329,11 +336,110 @@
this.groupBox2.Controls.Add(this.comboBox1); this.groupBox2.Controls.Add(this.comboBox1);
this.groupBox2.Location = new System.Drawing.Point(12, 100); this.groupBox2.Location = new System.Drawing.Point(12, 100);
this.groupBox2.Name = "groupBox2"; this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(519, 527); this.groupBox2.Size = new System.Drawing.Size(519, 623);
this.groupBox2.TabIndex = 3; this.groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false; this.groupBox2.TabStop = false;
this.groupBox2.Text = "Profiles"; 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 // label13
// //
this.label13.AutoSize = true; this.label13.AutoSize = true;
@@ -407,59 +513,11 @@
this.label12.TabIndex = 27; this.label12.TabIndex = 27;
this.label12.Text = "Separating character format"; this.label12.Text = "Separating character format";
// //
// 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;
//
// 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;
//
// Form1 // Form1
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(572, 695); this.ClientSize = new System.Drawing.Size(553, 764);
this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox2);
this.Controls.Add(this.button2); this.Controls.Add(this.button2);
this.Controls.Add(this.ok_button_master); this.Controls.Add(this.ok_button_master);
@@ -473,6 +531,8 @@
this.groupBox3.PerformLayout(); this.groupBox3.PerformLayout();
this.groupBox2.ResumeLayout(false); this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout(); 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.ResumeLayout(false);
this.web_server_settings_groupbox.PerformLayout(); this.web_server_settings_groupbox.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
@@ -523,5 +583,10 @@
private System.Windows.Forms.CheckBox web_server_checkbox; private System.Windows.Forms.CheckBox web_server_checkbox;
private System.Windows.Forms.TextBox web_server_port_textbox; private System.Windows.Forms.TextBox web_server_port_textbox;
private System.Windows.Forms.Label web_server_port_label; 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;
} }
} }
+7 -1
View File
@@ -83,6 +83,9 @@ namespace RadioDJViewer
{ {
web_server_checkbox.Checked = profile.WebServerEnabled; web_server_checkbox.Checked = profile.WebServerEnabled;
web_server_port_textbox.Text = (profile.WebServerPort > 0 ? profile.WebServerPort : 8080).ToString(); 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 { } catch { }
} }
@@ -139,7 +142,10 @@ namespace RadioDJViewer
MarqueeSeparator = separatingcharacterformatbox.Text, MarqueeSeparator = separatingcharacterformatbox.Text,
// Web server settings // Web server settings
WebServerEnabled = web_server_checkbox.Checked, 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,
ToastNotificationsEnabled = toast_check_box_enable.Checked
}; };
ProfileStorage.SaveProfile(profile); ProfileStorage.SaveProfile(profile);
// Refresh profile list // Refresh profile list
+157 -114
View File
@@ -1,128 +1,171 @@
/////////////// (function () {
// PARAMETERS // 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; function getTextContent(selector) {
const urlParams = new URLSearchParams(queryString); var element = document.querySelector(selector);
const refreshRate = parseInt(urlParams.get("refreshrate")) || 6; // Default 6 seconds if (!element || typeof element.textContent !== "string") {
return "";
// 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;
} }
return trimText(element.textContent);
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);
} }
if (artist !== currentArtist) { function trimText(value) {
currentArtist = artist; return String(value || "").replace(/^\s+|\s+$/g, "");
artistElement.classList.add("updating");
artistElement.textContent = artist || "Unknown Artist";
trackChanged = true;
setTimeout(() => {
artistElement.classList.remove("updating");
}, 400);
} }
// Return whether track changed (to trigger image update) function getRefreshRateMs() {
return trackChanged; 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) {
* Updates both the album art and background image with cache-busting timestamp var xhr = new XMLHttpRequest();
* Only called when track actually changes xhr.open("GET", path + "?v=" + new Date().getTime(), true);
*/ xhr.setRequestHeader("Cache-Control", "no-cache");
function updateAlbumArt() { xhr.onreadystatechange = function () {
const albumArt = document.querySelector(".album-art"); if (xhr.readyState !== 4) {
const backgroundImage = document.querySelector(".background-image"); return;
const timestamp = new Date().getTime(); }
const imageUrl = `${IMAGE_FILENAME}?v=${timestamp}`; if (xhr.status >= 200 && xhr.status < 300) {
callback(trimText(xhr.responseText));
albumArt.src = imageUrl; return;
backgroundImage.src = imageUrl; }
} callback(fallbackValue);
};
/** xhr.onerror = function () {
* Main refresh function - fetches data and updates if changed callback(fallbackValue);
*/ };
async function refresh() { try {
const data = await fetchSongData(); xhr.send(null);
if (data) { } catch (e) {
const trackChanged = updateSongDisplay(data.artist, data.title); callback(fallbackValue);
// Only update images when track actually changes
if (trackChanged) {
updateAlbumArt();
} }
} }
}
/////////////// function updateSongDisplay(data) {
// INITIALIZE // var titleElement = document.querySelector(".song-title");
/////////////// var artistElement = document.querySelector(".song-artist");
var trackChanged = false;
var nextTitle;
var nextArtist;
function init() { if (!titleElement || !artistElement || !data) {
// Load initial data immediately return false;
refresh(); }
// Set up refresh interval based on URL parameter or default nextTitle = trimText(data.title || "");
refreshInterval = setInterval(refresh, refreshRate * 1000); 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 (nextArtist !== currentArtist) {
if (document.readyState === "loading") { currentArtist = nextArtist;
document.addEventListener("DOMContentLoaded", init); artistElement.classList.add("updating");
} else { artistElement.textContent = currentArtist || "Unknown Artist";
init(); 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();
}
})();
+34
View File
@@ -127,6 +127,21 @@ body {
opacity: 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 */ /* Smooth fade animation for updates */
@keyframes fadeIn { @keyframes fadeIn {
from { from {
@@ -141,3 +156,22 @@ body {
.song-artist.updating { .song-artist.updating {
animation: fadeIn 0.4s ease-in-out; 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;
}