diff --git a/k-tv-backend/infra/src/jellyfin/provider.rs b/k-tv-backend/infra/src/jellyfin/provider.rs
index 74d2ede..78721e1 100644
--- a/k-tv-backend/infra/src/jellyfin/provider.rs
+++ b/k-tv-backend/infra/src/jellyfin/provider.rs
@@ -395,7 +395,7 @@ impl IMediaProvider for JellyfinMediaProvider {
impl JellyfinMediaProvider {
fn hls_url(&self, item_id: &MediaItemId, bitrate: u32) -> String {
format!(
- "{}/Videos/{}/master.m3u8?videoCodec=h264&audioCodec=aac&VideoBitRate={}&mediaSourceId={}&api_key={}",
+ "{}/Videos/{}/master.m3u8?videoCodec=h264&audioCodec=aac&VideoBitRate={}&mediaSourceId={}&SubtitleMethod=Hls&subtitleCodec=vtt&api_key={}",
self.config.base_url,
item_id.as_ref(),
bitrate,
diff --git a/k-tv-frontend/app/(main)/docs/page.tsx b/k-tv-frontend/app/(main)/docs/page.tsx
index 30071b5..287901d 100644
--- a/k-tv-frontend/app/(main)/docs/page.tsx
+++ b/k-tv-frontend/app/(main)/docs/page.tsx
@@ -131,7 +131,10 @@ const TOC = [
{ id: "recycle-policy", label: "Recycle policy" },
{ id: "import-export", label: "Import & export" },
{ id: "iptv", label: "IPTV export" },
- { id: "channel-password", label: "Channel passwords" },
+ { id: "access-control", label: "Access control" },
+ { id: "channel-logo", label: "Channel logo" },
+ { id: "webhooks", label: "Webhooks" },
+ { id: "admin", label: "Admin panel" },
{ id: "tv-page", label: "Watching TV" },
{ id: "troubleshooting", label: "Troubleshooting" },
];
@@ -545,6 +548,16 @@ Authorization: Bearer
files instead of hls.js.
+ Transcode settings
+
+ When transcoding is available (TRANSCODE_DIR is set),
+ a gear icon appears in the Dashboard header. Click it to open the{" "}
+ Transcode Settings{" "}
+ dialog, where you can adjust the cache cleanup TTL — how long
+ transcoded segment files are kept before the hourly cleanup removes
+ them.
+
+
Filter support
When the local files provider is active, the series picker and genre
@@ -588,10 +601,12 @@ Authorization: Bearer
content, and starts broadcasting immediately.
- Schedules are valid for 48 hours. K-TV does not regenerate them
- automatically — return to the Dashboard and click{" "}
- Generate whenever you
- want a fresh lineup.
+ Schedules are valid for 48 hours. If the channel's{" "}
+ Auto-schedule toggle is
+ enabled (in the edit sheet), the server regenerates the schedule
+ automatically when it expires. Otherwise, return to the Dashboard
+ and click Generate{" "}
+ whenever you want a fresh lineup.
@@ -696,6 +711,16 @@ Authorization: Bearer
"string[]",
"Jellyfin library / folder IDs. Find the ID in the Jellyfin URL when browsing a library. Leave empty to search all libraries.",
],
+ [
+ series_names,
+ "string[]",
+ "Only include episodes from the listed TV series (OR-combined). Jellyfin only.",
+ ],
+ [
+ search_term,
+ "string",
+ "Free-text search passed to the provider.",
+ ],
]}
/>
@@ -924,19 +949,40 @@ Output only valid JSON matching this structure:
{/* ---------------------------------------------------------------- */}
-
- Channel passwords
+
+ Access control
- Individual channels can be protected with an optional password. When
- set, TV viewers are prompted to enter the password before the stream
- plays. Channels without a password are always public.
+ Each channel has an access_mode field that controls who
+ can watch it. Set it in the edit sheet.
+ public,
+ "Anyone can watch. This is the default.",
+ ],
+ [
+ password_protected,
+ "Viewers must enter a password before the stream plays.",
+ ],
+ [
+ account_required,
+ "Viewers must be logged in to any K-TV account.",
+ ],
+ [
+ owner_only,
+ "Only the channel owner can watch.",
+ ],
+ ]}
+ />
Setting a password
- Enter a password in the Password{" "}
- field when creating a channel or editing it in the Dashboard. Leave
- the field blank to remove an existing password.
+ When access_mode is{" "}
+ password_protected, enter a value in the{" "}
+ Password field in the
+ edit sheet. Leave the field blank to remove an existing password.
@@ -947,6 +993,88 @@ Output only valid JSON matching this structure:
+ {/* ---------------------------------------------------------------- */}
+
+ Channel logo
+
+ A logo can be shown as a watermark overlay in the TV player. Set
+ these fields in the channel edit sheet:
+
+
+ Corner where the logo appears.{" "}
+ top_right (default) /{" "}
+ top_left /{" "}
+ bottom_left /{" "}
+ bottom_right.
+ >,
+ ],
+ [
+ "Logo opacity",
+ "A value from 0.0 (invisible) to 1.0 (fully opaque). Controls how prominent the watermark is.",
+ ],
+ ]}
+ />
+
+
+ {/* ---------------------------------------------------------------- */}
+
+ Webhooks
+
+ Channels can fire an HTTP POST to a URL of your choice when domain
+ events occur (e.g. a schedule is generated). Configure webhooks in
+ the edit sheet under the{" "}
+ Webhook section.
+
+
+ Preset formats
+
+
+ Template variables
+
+ Custom templates use Handlebars syntax. Available variables:
+
+ {"{{event}}"}, "Event name, e.g. schedule_generated."],
+ [{"{{timestamp}}"}, "ISO 8601 timestamp of the event."],
+ [{"{{data.item.title}}"}, "Title of the affected media item (where applicable)."],
+ ]}
+ />
+
+ Extra headers
+
+ Use webhook_headers to add custom HTTP headers to
+ every delivery — for example{" "}
+ Authorization: Bearer … for endpoints that require
+ authentication.
+
+
+ Poll interval
+
+ webhook_poll_interval_secs controls how often the
+ backend checks for pending webhook deliveries. Lower values mean
+ faster delivery but more database reads.
+
+
+
{/* ---------------------------------------------------------------- */}
Watching TV
@@ -1019,6 +1147,32 @@ Output only valid JSON matching this structure:
+ {/* ---------------------------------------------------------------- */}
+
+ Admin panel
+
+ The /admin route is available to any logged-in user.
+ It provides two live views into the running server:
+
+
+
+ Access requires a valid login session. Unauthenticated visitors are
+ redirected to the login page.
+
+
+
{/* ---------------------------------------------------------------- */}
Troubleshooting
diff --git a/k-tv-frontend/app/(main)/tv/page.tsx b/k-tv-frontend/app/(main)/tv/page.tsx
index 60b05af..0412ca0 100644
--- a/k-tv-frontend/app/(main)/tv/page.tsx
+++ b/k-tv-frontend/app/(main)/tv/page.tsx
@@ -429,6 +429,14 @@ function TvPageContent() {
case "M":
toggleMute();
break;
+ case "c":
+ case "C":
+ if (subtitleTracks.length > 0) {
+ setActiveSubtitleTrack((prev) =>
+ prev === -1 ? subtitleTracks[0].id : -1,
+ );
+ }
+ break;
default: {
if (e.key >= "0" && e.key <= "9") {
setChannelInput((prev) => {
@@ -464,6 +472,7 @@ function TvPageContent() {
channelCount,
switchChannel,
resetIdle,
+ subtitleTracks,
]);
// ------------------------------------------------------------------