diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb61bbf4..2946d261 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Stats page enhancements: Added "Now Playing" program information for active streams with smart polling that only fetches EPG data when programs are about to change (not on every stats refresh). Features include:
- Currently playing program title displayed with live broadcast indicator (green Radio icon)
- Expandable program descriptions via chevron button
+ - Progress bar showing elapsed and remaining time for currently playing programs
- Efficient POST-based API endpoint (`/api/epg/current-programs/`) supporting batch channel queries or fetching all channels
- Smart scheduling that fetches new program data 5 seconds after current program ends
- Only polls when active channel list changes, not on stats refresh
diff --git a/frontend/src/components/cards/StreamConnectionCard.jsx b/frontend/src/components/cards/StreamConnectionCard.jsx
index 5488aef5..e8b8a33b 100644
--- a/frontend/src/components/cards/StreamConnectionCard.jsx
+++ b/frontend/src/components/cards/StreamConnectionCard.jsx
@@ -11,6 +11,7 @@ import {
Center,
Flex,
Group,
+ Progress,
Select,
Stack,
Text,
@@ -549,6 +550,56 @@ const StreamConnectionCard = ({
)}
+ {/* Program progress bar */}
+ {currentProgram &&
+ isProgramDescExpanded &&
+ currentProgram.start_time &&
+ currentProgram.end_time &&
+ (() => {
+ const now = new Date();
+ const startTime = new Date(currentProgram.start_time);
+ const endTime = new Date(currentProgram.end_time);
+ const totalDuration = (endTime - startTime) / 1000; // in seconds
+ const elapsed = (now - startTime) / 1000; // in seconds
+ const remaining = (endTime - now) / 1000; // in seconds
+ const percentage = Math.min(
+ 100,
+ Math.max(0, (elapsed / totalDuration) * 100)
+ );
+
+ const formatProgramTime = (seconds) => {
+ const absSeconds = Math.abs(seconds);
+ const hours = Math.floor(absSeconds / 3600);
+ const minutes = Math.floor((absSeconds % 3600) / 60);
+ const secs = Math.floor(absSeconds % 60);
+ if (hours > 0) {
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
+ }
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
+ };
+
+ return (
+
+
+
+ {formatProgramTime(elapsed)} elapsed
+
+
+ {formatProgramTime(remaining)} remaining
+
+
+
+
+ );
+ })()}
+
{/* Add stream selection dropdown and preview button */}
{availableStreams.length > 0 && (