From ee64fc0b8a91d48609b85f7040ae1b8ed5c1f564 Mon Sep 17 00:00:00 2001
From: Gabriel Kaszewski
Date: Wed, 11 Mar 2026 22:30:05 +0100
Subject: [PATCH] feat: enhance schedule slot handling with episode details and
duration calculation
---
.../(main)/tv/components/schedule-overlay.tsx | 14 ++++
k-tv-frontend/app/(main)/tv/page.tsx | 69 ++++++++++++++-----
k-tv-frontend/hooks/use-tv.ts | 42 +++++++++--
3 files changed, 101 insertions(+), 24 deletions(-)
diff --git a/k-tv-frontend/app/(main)/tv/components/schedule-overlay.tsx b/k-tv-frontend/app/(main)/tv/components/schedule-overlay.tsx
index 85971a4..f6a1974 100644
--- a/k-tv-frontend/app/(main)/tv/components/schedule-overlay.tsx
+++ b/k-tv-frontend/app/(main)/tv/components/schedule-overlay.tsx
@@ -3,7 +3,12 @@ import { cn } from "@/lib/utils";
export interface ScheduleSlot {
id: string;
+ /** Headline: series name for episodes, film title for everything else. */
title: string;
+ /** Secondary line: "S1 · E3 · Episode Title" for episodes, year for movies. */
+ subtitle?: string | null;
+ /** Rounded slot duration in minutes. */
+ durationMins: number;
startTime: string; // "HH:MM"
endTime: string; // "HH:MM"
isCurrent?: boolean;
@@ -50,8 +55,17 @@ export function ScheduleOverlay({ channelName, slots }: ScheduleOverlayProps) {
>
{slot.title}
+ {slot.subtitle && (
+
+ {slot.subtitle}
+
+ )}
{slot.startTime} – {slot.endTime}
+ {" · "}{slot.durationMins}m
diff --git a/k-tv-frontend/app/(main)/tv/page.tsx b/k-tv-frontend/app/(main)/tv/page.tsx
index 82d8845..cbdf8a0 100644
--- a/k-tv-frontend/app/(main)/tv/page.tsx
+++ b/k-tv-frontend/app/(main)/tv/page.tsx
@@ -11,7 +11,7 @@ import {
NoSignal,
} from "./components";
import type { SubtitleTrack } from "./components/video-player";
-import { Maximize2, Minimize2, Volume2, VolumeX } from "lucide-react";
+import { Maximize2, Minimize2, Volume1, Volume2, VolumeX } from "lucide-react";
import { useAuthContext } from "@/context/auth-context";
import { useChannels, useCurrentBroadcast, useEpg } from "@/hooks/use-channels";
import {
@@ -76,12 +76,17 @@ export default function TvPage() {
}
}, []);
- // Volume / mute
+ // Volume control
+ const [volume, setVolume] = useState(1); // 0.0 – 1.0
const [isMuted, setIsMuted] = useState(false);
+ const [showVolumeSlider, setShowVolumeSlider] = useState(false);
useEffect(() => {
- if (videoRef.current) videoRef.current.muted = isMuted;
- }, [isMuted]);
+ if (!videoRef.current) return;
+ videoRef.current.muted = isMuted;
+ videoRef.current.volume = volume;
+ }, [isMuted, volume]);
const toggleMute = useCallback(() => setIsMuted((m) => !m), []);
+ const VolumeIcon = isMuted || volume === 0 ? VolumeX : volume < 0.5 ? Volume1 : Volume2;
// Channel jump by number (e.g. press "1","4" → jump to ch 14 after 1.5 s)
const [channelInput, setChannelInput] = useState("");
@@ -137,10 +142,11 @@ export default function TvPage() {
const resetIdle = useCallback(() => {
setShowOverlays(true);
if (idleTimer.current) clearTimeout(idleTimer.current);
- idleTimer.current = setTimeout(
- () => setShowOverlays(false),
- IDLE_TIMEOUT_MS,
- );
+ idleTimer.current = setTimeout(() => {
+ setShowOverlays(false);
+ setShowVolumeSlider(false);
+ setShowSubtitlePicker(false);
+ }, IDLE_TIMEOUT_MS);
// Resume playback if autoplay was blocked (e.g. on page refresh with no prior interaction)
videoRef.current?.play().catch(() => {});
}, []);
@@ -395,15 +401,44 @@ export default function TvPage() {
)}
-
+ {/* Volume control */}
+
+
+
+ {showVolumeSlider && (
+
+
{
+ const v = Number(e.target.value) / 100;
+ setVolume(v);
+ setIsMuted(v === 0);
+ }}
+ className="w-full accent-white"
+ />
+
+
+
+ {isMuted ? "0" : Math.round(volume * 100)}%
+
+
+
+ )}
+