Skip to content

Add native IPTV Live TV support#16

Open
cat5edopeHA wants to merge 13 commits into
kaleidal:mainfrom
cat5edopeHA:feature/native-iptv
Open

Add native IPTV Live TV support#16
cat5edopeHA wants to merge 13 commits into
kaleidal:mainfrom
cat5edopeHA:feature/native-iptv

Conversation

@cat5edopeHA

@cat5edopeHA cat5edopeHA commented Jun 23, 2026

Copy link
Copy Markdown

Summary

Adds native IPTV / M3U Live TV support with XMLTV guide data and a TV-guide-style EPG grid.

Highlights

  • Add IPTV source management for M3U + XMLTV URLs
  • Parse and refresh IPTV playlists
  • Parse XMLTV guide metadata
  • Persist refreshed channel/group state locally
  • Add Live TV guide grid:
    • channel/logo rail
    • horizontal programme blocks
    • current-time marker
    • current/future programme styling
  • Add regression tests for IPTV cache and EPG layout

Verification

  • bun test packages/app/src/lib/iptv/guideGrid.test.ts packages/app/src/lib/iptv/*.test.ts
  • bun run check:app
  • bun run check:desktop
  • bun run build:desktop
  • Packaged and verified in a macOS VM

Summary by CodeRabbit

  • New Features
    • Added a Live TV page with source add/edit/remove, channel refresh, group filtering, favorites, and a current/next guide grid.
    • Added a toggleable Home “Live TV” shortcut that navigates to the Live TV page.
    • Enabled dedicated live playback in the player with live-specific UI/loading behavior.
  • Security & Reliability
    • Improved IPTV text fetching with strict URL checks, safer redirect handling, timeouts, and maximum download-size limits.
  • Performance
    • Added local caching for IPTV refresh results and guide data to speed up reloads.
  • Analytics
    • Updated playback analytics to report live mode; added analytics for Live TV navigation and shortcut toggles.
  • Tests
    • Added/expanded IPTV and Live TV test coverage for parsing, caching, and scheduling.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a complete Live TV IPTV flow: source parsing, secure fetching, storage and caching, guide rendering, live playback gating, and home/routing integration.

Changes

Live TV feature

Layer / File(s) Summary
IPTV contracts and helpers
packages/app/src/lib/iptv/types.ts, packages/app/src/lib/iptv/utils.ts, packages/app/src/lib/iptv/xtream.ts
Shared IPTV and XMLTV types are defined, along with text normalization, slug generation, quoted-attribute parsing, and Xtream URL builders.
M3U and XMLTV parsers
packages/app/src/lib/iptv/m3u.ts, packages/app/src/lib/iptv/m3u.test.ts, packages/app/src/lib/iptv/xmltv.ts, packages/app/src/lib/iptv/xmltv.test.ts
parseM3U and parseXmltv convert playlist and guide text into structured data, with now/next lookup, programme counting, and parser coverage for quoted values, malformed inputs, and matching fallbacks.
IPTV fetch and IPC
packages/app/src/lib/iptv/fetch.ts, packages/app/src/lib/iptv/fetch.test.ts, apps/desktop/electron/preload.cjs, apps/desktop/electron/services/mainIpc.cjs
iptvFetchText validates URLs, uses Electron IPC when available, falls back to browser fetch, and the Electron main process adds secure IPTV fetch handling with the preload bridge.
Source store, refresh flow, and cache
packages/app/src/lib/iptv/store.ts, packages/app/src/lib/iptv/store.test.ts, packages/app/src/lib/iptv/refresh.ts, packages/app/src/lib/iptv/refresh.test.ts, packages/app/src/lib/iptv/cache.ts, packages/app/src/lib/iptv/cache.test.ts
IPTV sources are persisted in localStorage, refreshed into channel/guide results, and cached with source matching, validation, and Xtream cache invalidation.
Guide grid layout
packages/app/src/lib/iptv/guideGrid.ts, packages/app/src/lib/iptv/guideGrid.test.ts
IPTV channels and XMLTV data are transformed into viewport-clipped programme tiles with temporal state labels and now-line positioning.
LiveTV page
packages/app/src/pages/live/LiveTV.svelte, packages/app/src/pages/live/liveHelpers.ts, packages/app/src/pages/live/liveHelpers.test.ts, packages/app/src/pages/live/components/*
The LiveTV page and its helpers manage IPTV sources, refresh/loading states, filtering, guide rendering, playback navigation, favorites, and source-management modals.
Routing and home entry points
packages/app/src/lib/stores/router.ts, packages/app/src/App.svelte, packages/app/src/components/home/SearchBar.svelte
The live route is added, the LiveTV page is mounted in app routing, and a Live TV shortcut is added to the home search bar with stored-preference synchronization.
Live TV shortcut preference
packages/app/src/lib/home/searchBarSettings.ts, packages/app/src/lib/home/searchBarSettings.test.ts, packages/app/src/components/home/modals/settings/PreferencesSection.svelte
A localStorage-backed Live TV shortcut preference is added, along with a preferences toggle and change event flow.
Player live-mode handling
packages/app/src/pages/player/Player.svelte, packages/app/src/pages/player/components/PlayerLoadingScreen.svelte, packages/app/src/pages/player/playerAnalytics.ts, packages/app/src/pages/player/types.ts, packages/app/src/pages/player/playerSessionLoader.ts, packages/app/src/pages/player/videoSession.ts
The player gains live-mode props and gates playback, Trakt, chapters, watch-party, and episode logic; loading, analytics, session loading, and direct HLS playback are updated for live playback.
Smoke test and TypeScript config
scripts/smoke-iptv-dispatcharr.mjs, packages/app/tsconfig.json
A CLI smoke test validates live IPTV endpoints and parsing, and test files are excluded from TypeScript compilation.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SearchBar as SearchBar.svelte
  participant LiveTV as LiveTV.svelte
  participant Refresh as refreshIptvSource
  participant Cache as cache.ts
  participant Player as Player.svelte

  User->>SearchBar: click Live TV
  SearchBar->>LiveTV: navigate("live")
  User->>LiveTV: refresh source
  LiveTV->>Refresh: refreshIptvSource(source)
  Refresh-->>LiveTV: refresh result
  LiveTV->>Cache: persistIptvRefreshResult(source, result)
  User->>LiveTV: play channel
  LiveTV->>Player: navigate("player", liveMode=true)
  Player->>Player: gate live playback logic
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Poem

🐇 Hop hop, the channels sing,
Live guide tiles are in full swing.
M3U streams and EPG glow,
Bunny paws make playback flow.
One click, the rabbit takes a bow—
the Live TV meadow hums right now! 📺

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding native IPTV Live TV support with guide and source management.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cat5edopeHA

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (2)
packages/app/src/lib/iptv/xmltv.test.ts (1)

15-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a malformed programme tolerance test.

Please add a case where one <programme> has an invalid start/stop timestamp and verify valid programmes still parse, to lock in partial-recovery behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/lib/iptv/xmltv.test.ts` around lines 15 - 73, Add a new test
case within the describe("parseXmltv") block that validates partial recovery
behavior when encountering malformed data. Create a test XML string containing
both a programme with invalid start/stop timestamps and a valid programme, then
verify that parseXmltv still successfully parses the valid programme while
gracefully handling the malformed one. This ensures the parser can recover from
partial data corruption and continues processing valid entries.
packages/app/src/lib/iptv/m3u.test.ts (1)

44-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for group ID collision cases.

Please add a case with two distinct group-title values that normalize to the same slug and assert unique group ids in output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/lib/iptv/m3u.test.ts` around lines 44 - 71, Add a new
regression test case after the existing test functions in the test suite to
verify that group ID collisions are handled correctly. Create a test that uses a
playlist with two distinct group-title values that normalize to the same slug
(for example, "News Group" and "News-Group" or "tech news" and "tech-news"),
then call parseM3U and assert that the result contains groups with unique group
IDs even though the normalized slugs would collide. This ensures the parseM3U
function properly handles slug collision scenarios by generating distinct
identifiers for groups with different original titles.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/electron/services/mainIpc.cjs`:
- Around line 91-121: The DNS validation in assertSafeIptvFetchUrl validates
resolved addresses at one moment, but getSafeIptvFetchResponse later calls fetch
which performs a separate DNS resolution on the hostname, creating a window for
DNS rebinding attacks. Modify assertSafeIptvFetchUrl to return the validated
resolved IP addresses instead of just throwing errors, then update
getSafeIptvFetchResponse to use the returned resolved IP address directly in the
fetch call instead of passing the original hostname URL to fetch, ensuring the
same validated address is actually used for the network connection.
- Around line 275-313: The IPTV fetch response streams are not being properly
canceled when errors are thrown, causing potential socket and memory leaks. You
need to cancel the response body when throwing an error on the non-OK response
check (when response.ok is false), and cancel the reader stream when throwing on
the size overflow condition (when totalBytes exceeds maxBytes in the while
loop). Additionally, ensure the arrayBuffer size overflow path (when
arrayBuffer.byteLength exceeds maxBytes) also properly cleans up any resources
before throwing. Use the appropriate cancellation/abort methods for the response
and reader objects before each throw statement to ensure streams are properly
cleaned up on all error paths.

In `@packages/app/src/lib/iptv/cache.test.ts`:
- Line 100: The assertion on line 100 stringifies the MemoryStorage instance
itself, but this doesn't reveal the actual cached data because the private Map
containing the values is not included in the JSON serialization. Instead of
checking if the stringified storage does not contain "programmesByChannel",
access the actual stored cache values from the MemoryStorage instance and verify
that "programmesByChannel" is not present in the real stored data. Check the
MemoryStorage implementation to determine the appropriate method or property to
retrieve the actual cached values.

In `@packages/app/src/lib/iptv/cache.ts`:
- Around line 140-145: The cache sanitization validation in the conditional
statement (where sanitizeChannel, sanitizeGroup, and sanitizeStats are called)
only checks for null values but does not verify sourceId consistency between the
parent object and individual channels/groups. Add additional checks to the
existing if condition to ensure that every channel object has a sourceId
matching the parent value.sourceId and every group object has a sourceId
matching the parent value.sourceId, returning null if any mismatch is found.
This validation should be added to the same condition alongside the existing
null checks.

In `@packages/app/src/lib/iptv/fetch.ts`:
- Around line 52-55: The current implementation in the fetch function calls
response.text() which buffers the entire response payload into memory before
checking against maxBytes, defeating the size limit's purpose for responses
without a content-length header. Replace the response.text() buffering approach
with streaming via response.body?.getReader() to incrementally read the response
in chunks, accumulating the byte length as you read. Check if the accumulated
byte length exceeds maxBytes during the streaming process and throw an error
immediately without buffering the remaining data. Only decode the accumulated
buffer to text after confirming the total size is within the maxBytes limit.

In `@packages/app/src/lib/iptv/guideGrid.ts`:
- Around line 32-52: The resolveGuideChannelId function accepts tvgId when
either guide.channels or guide.programmesByChannel contains that key (via the
hasGuideChannelId check), but the getNowNext function in xmltv.ts resolves tvgId
using guide.channels only. This inconsistency causes channels to be found in the
grid but fail to resolve during playback. Create a shared channel resolver
function that both resolveGuideChannelId and getNowNext can use, ensuring they
follow the same resolution logic. The shared resolver should define whether
tvgId lookups check both guide.channels and guide.programmesByChannel, or only
guide.channels, consistently across both call sites.
- Around line 91-102: In the code block where startMs and stopMs are calculated
from programme.start and programme.stop, add an early validation check to detect
malformed programme intervals where stopMs is less than or equal to startMs, and
return null if this condition is found. This check must occur before the
viewport clipping and percentage calculations (roundPercent calls for
leftPercent and widthPercent) to prevent invalid programme tiles from being
created due to zero or negative widthPercent values.

In `@packages/app/src/lib/iptv/m3u.ts`:
- Around line 105-109: The group ID generation in the map function uses only the
slugified name via slugForId(name), which causes different original names that
normalize to the same slug (like "Café" and "Cafe") to produce duplicate IDs.
Include the original group name parameter in the ID construction alongside the
slug to ensure uniqueness, so that even if names normalize identically, the
original names keep the IDs distinct and prevent unintended group merging
downstream.

In `@packages/app/src/lib/iptv/store.ts`:
- Around line 58-67: The persistSources function's storage.setItem() call can
throw exceptions (QuotaExceededError, SecurityError) but lacks error handling,
and setSources calls persistSources before updating in-memory state, causing
complete operation failure if storage is unavailable. Wrap the storage.setItem()
call inside persistSources with a try-catch block to gracefully handle storage
errors, then reorder setSources to call iptvSources.set(sources) before
persistSources(sources) so the in-memory state is always updated even when
storage persistence fails.

In `@packages/app/src/lib/iptv/xmltv.ts`:
- Around line 154-171: The parseXmltvTime function calls on lines 164-165 can
throw exceptions that abort the entire guide parse when encountering a malformed
programme entry. Wrap both parseXmltvTime calls (for start and stop times) in a
try-catch block within the while loop that processes programmes, and use
continue to skip to the next programme if parsing fails, ensuring that one
invalid entry does not invalidate the entire guide.

In `@packages/app/src/pages/live/LiveTV.svelte`:
- Around line 363-369: The formatLoadedAt function does not guard against
invalid date strings, which can cause the Intl.DateTimeFormat().format() call to
throw an error and crash the page render. Add validation in the formatLoadedAt
function to check if the date string is valid before formatting it, returning a
fallback value for invalid dates. Additionally, add date validation for the
loadedAt field in packages/app/src/lib/iptv/cache.ts to ensure the cache
sanitization only accepts valid date strings and rejects or sanitizes
malformed/legacy localStorage entries.

In `@packages/app/src/pages/player/Player.svelte`:
- Around line 1276-1278: The reactive condition in the watch party modal close
logic on line 1276 is missing a check for local mode. Add `$localMode` to the OR
condition alongside `liveMode`, `embedSrc`, and the cloud sync check so that the
watch party modal is properly closed when local mode is active, preventing it
from unexpectedly reopening when local mode is disabled.

In `@scripts/smoke-iptv-dispatcharr.mjs`:
- Around line 46-47: After calling parseM3U(m3u.text, "smoke"), add a validation
check to verify that the parsed result contains channels. If the parseM3U
function returns zero channels (or an empty result), the smoke test should fail
by throwing an error or exiting with a non-zero code. This ensures the smoke
test properly validates the contract expected by the app code in refresh.ts and
doesn't succeed with an invalid empty playlist.
- Around line 15-24: The fetchText function lacks a timeout mechanism for
network requests, which can cause the smoke test to hang indefinitely on stalled
endpoints. Add an AbortController with a bounded timeout to the fetch call in
fetchText. Create an AbortController instance, set up a timeout using setTimeout
that calls abort() after a reasonable duration (e.g., 30 seconds), pass the
abort signal to the fetch options, and handle the AbortError in the catch block
to provide a clear timeout error message. Make sure to clear the timeout after
the response completes to prevent memory leaks.

---

Nitpick comments:
In `@packages/app/src/lib/iptv/m3u.test.ts`:
- Around line 44-71: Add a new regression test case after the existing test
functions in the test suite to verify that group ID collisions are handled
correctly. Create a test that uses a playlist with two distinct group-title
values that normalize to the same slug (for example, "News Group" and
"News-Group" or "tech news" and "tech-news"), then call parseM3U and assert that
the result contains groups with unique group IDs even though the normalized
slugs would collide. This ensures the parseM3U function properly handles slug
collision scenarios by generating distinct identifiers for groups with different
original titles.

In `@packages/app/src/lib/iptv/xmltv.test.ts`:
- Around line 15-73: Add a new test case within the describe("parseXmltv") block
that validates partial recovery behavior when encountering malformed data.
Create a test XML string containing both a programme with invalid start/stop
timestamps and a valid programme, then verify that parseXmltv still successfully
parses the valid programme while gracefully handling the malformed one. This
ensures the parser can recover from partial data corruption and continues
processing valid entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6bbc4d26-8937-40b7-b6eb-dcc914eef927

📥 Commits

Reviewing files that changed from the base of the PR and between cd0f960 and d8e13bb.

📒 Files selected for processing (27)
  • apps/desktop/electron/preload.cjs
  • apps/desktop/electron/services/mainIpc.cjs
  • packages/app/src/App.svelte
  • packages/app/src/components/home/SearchBar.svelte
  • packages/app/src/lib/iptv/cache.test.ts
  • packages/app/src/lib/iptv/cache.ts
  • packages/app/src/lib/iptv/fetch.ts
  • packages/app/src/lib/iptv/guideGrid.test.ts
  • packages/app/src/lib/iptv/guideGrid.ts
  • packages/app/src/lib/iptv/m3u.test.ts
  • packages/app/src/lib/iptv/m3u.ts
  • packages/app/src/lib/iptv/refresh.test.ts
  • packages/app/src/lib/iptv/refresh.ts
  • packages/app/src/lib/iptv/store.test.ts
  • packages/app/src/lib/iptv/store.ts
  • packages/app/src/lib/iptv/types.ts
  • packages/app/src/lib/iptv/utils.ts
  • packages/app/src/lib/iptv/xmltv.test.ts
  • packages/app/src/lib/iptv/xmltv.ts
  • packages/app/src/lib/stores/router.ts
  • packages/app/src/pages/live/LiveTV.svelte
  • packages/app/src/pages/player/Player.svelte
  • packages/app/src/pages/player/components/PlayerLoadingScreen.svelte
  • packages/app/src/pages/player/playerAnalytics.ts
  • packages/app/src/pages/player/types.ts
  • packages/app/tsconfig.json
  • scripts/smoke-iptv-dispatcharr.mjs

Comment thread apps/desktop/electron/services/mainIpc.cjs Outdated
Comment thread apps/desktop/electron/services/mainIpc.cjs Outdated
Comment thread packages/app/src/lib/iptv/cache.test.ts Outdated
Comment thread packages/app/src/lib/iptv/cache.ts
Comment thread packages/app/src/lib/iptv/fetch.ts Outdated
Comment thread packages/app/src/lib/iptv/xmltv.ts
Comment thread packages/app/src/pages/live/LiveTV.svelte Outdated
Comment thread packages/app/src/pages/player/Player.svelte Outdated
Comment thread scripts/smoke-iptv-dispatcharr.mjs Outdated
Comment thread scripts/smoke-iptv-dispatcharr.mjs
@krissedout krissedout self-assigned this Jun 23, 2026
@krissedout krissedout linked an issue Jun 23, 2026 that may be closed by this pull request
3 tasks
@krissedout krissedout added the enhancement New feature or request label Jun 23, 2026
@krissedout

krissedout commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Hi @cat5edopeHA, thank you for your contribution! Took a quick look so far, a few things stand out:

The TV icon in the nav should be opt-in via Settings, not on by default, it's a niche feature compared to everything else up there.

The Live TV page needs to match the shell/chrome of the rest of the app: see Home, Library, and Meta as reference. Innovative layout is fine as long as you're reusing the existing design tokens and patterns (modals, posters, player) instead of introducing new ones (unless absolutely necessary), avoid overly excessive AI patterns such as sidebars, cards, missing hierarchy...

No judgment if this was built with AI tools, it just means it needs a human pass on the UI specifically to actually match Raffi instead of defaulting to generic patterns.

I haven't reviewed the code yet as I want to get the user-facing parts straightened out first before going deeper into the backend, since changes there might shift things around anyway.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/lib/iptv/cache.ts (1)

265-273: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Avoid persisting Xtream credentials inside cached channel URLs.

Line 269 stores result.channels verbatim. Xtream stream URLs can include plaintext credentials, which then get written to localStorage even though cache metadata is fingerprinted. Please sanitize/redact Xtream channel URLs before persistence (with safe reconstruction on read), or disable Xtream channel caching until that path is safe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/lib/iptv/cache.ts` around lines 265 - 273, The
StoredIptvRefreshResult object stores result.channels directly without
sanitizing Xtream stream URLs that may contain plaintext credentials, creating a
security risk when persisted to localStorage. Before storing channels in the
cached result object, implement URL sanitization logic to redact or remove
credentials from Xtream URLs, ensuring credentials are not written to the cache.
Add corresponding deserialization logic during cache reads to reconstruct the
safe URLs, or alternatively disable channel caching for Xtream sources entirely
if sanitization cannot be reliably implemented.
🧹 Nitpick comments (1)
packages/app/src/pages/live/LiveTV.svelte (1)

760-771: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Source manager modal: Escape won’t close it for keyboard users and focus isn’t trapped.

The onkeydown Escape handler is bound to the backdrop div (Lines 766-768). On open, focus stays on the trigger button outside this subtree, so the handler never receives the keydown and Escape does nothing until the backdrop itself is focused. Focus is also not moved into the dialog and not trapped. The visible X button (Lines 787-793) is still tab-reachable, so this is a degradation rather than a hard block.

Suggested fix: window-level Escape + focus the dialog on open
+<svelte:window
+    onkeydown={(event) => {
+        if (showSourceManager && event.key === "Escape") showSourceManager = false;
+    }}
+/>
+
 {`#if` showSourceManager}
     <div
         class="fixed inset-0 z-[220] flex items-center justify-center bg-black/70 p-4 backdrop-blur-sm"
         onclick={(event) => {
             if (event.currentTarget === event.target) showSourceManager = false;
         }}
-        onkeydown={(event) => {
-            if (event.key === "Escape") showSourceManager = false;
-        }}
         role="button"
         tabindex="0"
     >
         <section
             class="max-h-[92vh] w-full max-w-[1040px] overflow-y-auto rounded-[28px] border border-white/10 bg-[`#2b2b2b`]/95 p-5 shadow-[0_32px_100px_rgba(0,0,0,0.48)] backdrop-blur-xl"
             role="dialog"
             aria-modal="true"
             tabindex="-1"
+            use:focusOnMount
         >

(focusOnMount = a tiny action that calls node.focus(); pair with a focus trap for full keyboard support.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/pages/live/LiveTV.svelte` around lines 760 - 771, The Escape
key handler attached to the backdrop div element (in the onkeydown binding)
won't work because focus remains on the trigger button outside the modal, so the
div never receives keydown events. Move the Escape key handling to a
window-level listener instead, and add focus management to automatically focus
the modal dialog when showSourceManager becomes true. You can use an action like
focusOnMount that calls node.focus() when the modal element mounts, ensuring
keyboard navigation begins inside the dialog rather than on the external trigger
button.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/pages/live/LiveTV.svelte`:
- Around line 656-756: The channel guide currently renders all entries in the
guideRows array as DOM nodes, which causes performance issues with large
playlists. Every 60 seconds when guideNow updates, it triggers a full
recomputation of guideViewport and rebuilds the entire guideRows array, forcing
a complete re-render of all channel rows. Implement virtualization/windowing for
the channel rail (the section iterating over guideRows with the channel logos)
and the timeline rows section (the section iterating over guideRows with the
programmes) to only render the visible channels within the viewport.
Alternatively, implement pagination by capping the number of visibleChannels
rendered in guideRows rather than rendering all channels, so the re-render cost
is bounded by the viewport size instead of total channel count.

---

Outside diff comments:
In `@packages/app/src/lib/iptv/cache.ts`:
- Around line 265-273: The StoredIptvRefreshResult object stores result.channels
directly without sanitizing Xtream stream URLs that may contain plaintext
credentials, creating a security risk when persisted to localStorage. Before
storing channels in the cached result object, implement URL sanitization logic
to redact or remove credentials from Xtream URLs, ensuring credentials are not
written to the cache. Add corresponding deserialization logic during cache reads
to reconstruct the safe URLs, or alternatively disable channel caching for
Xtream sources entirely if sanitization cannot be reliably implemented.

---

Nitpick comments:
In `@packages/app/src/pages/live/LiveTV.svelte`:
- Around line 760-771: The Escape key handler attached to the backdrop div
element (in the onkeydown binding) won't work because focus remains on the
trigger button outside the modal, so the div never receives keydown events. Move
the Escape key handling to a window-level listener instead, and add focus
management to automatically focus the modal dialog when showSourceManager
becomes true. You can use an action like focusOnMount that calls node.focus()
when the modal element mounts, ensuring keyboard navigation begins inside the
dialog rather than on the external trigger button.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9b79eb1b-b546-4539-aab6-256c0cb72f9a

📥 Commits

Reviewing files that changed from the base of the PR and between d8e13bb and 436f0f5.

📒 Files selected for processing (24)
  • apps/desktop/electron/services/mainIpc.cjs
  • packages/app/src/components/home/SearchBar.svelte
  • packages/app/src/components/home/modals/settings/PreferencesSection.svelte
  • packages/app/src/lib/home/searchBarSettings.test.ts
  • packages/app/src/lib/home/searchBarSettings.ts
  • packages/app/src/lib/iptv/cache.test.ts
  • packages/app/src/lib/iptv/cache.ts
  • packages/app/src/lib/iptv/fetch.test.ts
  • packages/app/src/lib/iptv/fetch.ts
  • packages/app/src/lib/iptv/guideGrid.test.ts
  • packages/app/src/lib/iptv/guideGrid.ts
  • packages/app/src/lib/iptv/m3u.test.ts
  • packages/app/src/lib/iptv/m3u.ts
  • packages/app/src/lib/iptv/refresh.test.ts
  • packages/app/src/lib/iptv/refresh.ts
  • packages/app/src/lib/iptv/store.test.ts
  • packages/app/src/lib/iptv/store.ts
  • packages/app/src/lib/iptv/types.ts
  • packages/app/src/lib/iptv/xmltv.test.ts
  • packages/app/src/lib/iptv/xmltv.ts
  • packages/app/src/lib/iptv/xtream.ts
  • packages/app/src/pages/live/LiveTV.svelte
  • packages/app/src/pages/player/Player.svelte
  • scripts/smoke-iptv-dispatcharr.mjs
✅ Files skipped from review due to trivial changes (1)
  • packages/app/src/lib/iptv/xtream.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/app/src/lib/iptv/xmltv.test.ts
  • packages/app/src/lib/iptv/m3u.test.ts
  • packages/app/src/lib/iptv/refresh.ts
  • apps/desktop/electron/services/mainIpc.cjs
  • packages/app/src/lib/iptv/m3u.ts
  • scripts/smoke-iptv-dispatcharr.mjs
  • packages/app/src/lib/iptv/fetch.ts
  • packages/app/src/lib/iptv/store.ts
  • packages/app/src/lib/iptv/xmltv.ts
  • packages/app/src/pages/player/Player.svelte

Comment thread packages/app/src/pages/live/LiveTV.svelte Outdated
@krissedout

krissedout commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Hi, reviewed the new changes, we're still not quite there. A few specific asks:

  • The Live TV page should feel integrated into Raffi, not like a separate section. Reuse the navigation patterns from other pages (by other pages I specifically mean Home and Library/Lists) instead of introducing new chrome as raffi is an SPA.
    • Don't reinvent the header, reuse the header (SearchBar.svelte) from Home and Lists (if you're going for the gradient like Lists recolor it if you want differentiation), same with modals/forms elsewhere.
      • The Live TV page also doesn't need its own search box, the existing search bar in the navbar already searches titles on Home, it could just filter channels instead of searching through titles when you're on this page instead of duplicating the input.
      • For source switching something that comes to mind is instead of a horizontal list, you could number sources and surface them as a compact selector to the left of the search bar (think a small dropdown/badge, not a full panel, or maybe replace the search icon?), just an idea, if you've got a cleaner approach go for it.
  • Avoid cards/containers, content is preferred to sit directly on the page, same as the title rows on Home, though for the channel list I think it is fine to have it in a container, however stuff like the navigation etc should definitely not be.
  • Custom components are also preferred instead of relying on browser defaults such as dropdowns

If you think, of course, that it would be better to have the search or selectors displayed in another way I'm open to that too, but you must use at least the left side (raffi icon) and right side (navigation buttons, though addons etc could be hidden on the TV page, in other words the navigation can be dynamic as long as it looks like it's part of the same identity)

On the code side: keep IPTV-specific logic out of general helper files, it'll hurt maintainability long-term. Also try to keep files under ~500-600 lines, split them into components/helpers the way other page folders already do, you can use those as reference for the pattern.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/pages/live/LiveTV.svelte (1)

152-157: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reset the channel query when hydrating a different source.

hydrateStoredSource() clears the group filter but keeps searchQuery. If I switch to an unrefreshed source, the old query stays applied while the input is disabled, and the newly refreshed source can immediately render the "no-results" state for the stale filter.

Also applies to: 274-280

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/pages/live/LiveTV.svelte` around lines 152 - 157,
`hydrateStoredSource()` in LiveTV.svelte resets the selected group but leaves
the existing channel search filter in place, which can carry a stale query into
a newly hydrated source. Update the source hydration flow so that when
`hydrateStoredSource()` switches to a different source, it also clears
`searchQuery` alongside `selectedGroup`, keeping the query state in sync with
the disabled input and avoiding an immediate no-results view. Make the same
reset in the other source-hydration path referenced by the review so both code
paths behave consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/components/home/SearchBar.svelte`:
- Around line 763-766: The Play button in SearchBar.svelte has the wrong
accessible name and is currently announced as “addons” even though it triggers
openPlayModal. Update the button’s aria-label (or equivalent accessible text
source) in the relevant button markup so screen readers announce the correct
Play action, using the button definition tied to openPlayModal.

In `@packages/app/src/pages/live/components/LiveGuide.svelte`:
- Around line 83-91: The channel logo error handling in LiveGuide.svelte hides
the broken <img> but never shows the Tv fallback, leaving empty tiles. Update
the row.channel.logo rendering branch so the image failure path in the LiveGuide
component switches to the existing fallback icon/UI instead of only setting
display:none on the image, preserving a visible tile when the logo URL is
invalid.

---

Outside diff comments:
In `@packages/app/src/pages/live/LiveTV.svelte`:
- Around line 152-157: `hydrateStoredSource()` in LiveTV.svelte resets the
selected group but leaves the existing channel search filter in place, which can
carry a stale query into a newly hydrated source. Update the source hydration
flow so that when `hydrateStoredSource()` switches to a different source, it
also clears `searchQuery` alongside `selectedGroup`, keeping the query state in
sync with the disabled input and avoiding an immediate no-results view. Make the
same reset in the other source-hydration path referenced by the review so both
code paths behave consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba3a3a9c-2454-4dd2-8ed6-afa5dcf7a8d0

📥 Commits

Reviewing files that changed from the base of the PR and between b751034 and c6b8187.

📒 Files selected for processing (8)
  • packages/app/src/components/home/SearchBar.svelte
  • packages/app/src/pages/live/LiveTV.svelte
  • packages/app/src/pages/live/components/LiveEmptyState.svelte
  • packages/app/src/pages/live/components/LiveGroupFilter.svelte
  • packages/app/src/pages/live/components/LiveGuide.svelte
  • packages/app/src/pages/live/components/LiveSourceManager.svelte
  • packages/app/src/pages/live/components/LiveSourceSelector.svelte
  • packages/app/src/pages/live/liveHelpers.ts

Comment thread packages/app/src/components/home/SearchBar.svelte
Comment thread packages/app/src/pages/live/components/LiveGuide.svelte Outdated
@krissedout

krissedout commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Alright this is seeming way better now! A few more nitpicks:
The Live TV Sources modal has a border and isnt translucent like the other modals (Settings modal for example)

This + button here is redundant as the source add side panel is always visible (either make it Modal in Modal (like import from stremio) for adding sources or remove the +)
image

The Close button is also a different button style, in general try to follow the existing styles from other parts of the app that solve the same issue (modals, etc)
image
image

ffprobe seems to just not work for most streams (I do not have a personal m3u list so I can't really test whether it works for the more complete ones but the general public ones seem to not work) so I can't get to playback, maybe it's just my list but VLC seems to handle it fine, also here a good thing would be if we could show failure instantly instead of waiting for timeout as the server does log the failure but the client has no way of showing it.

Logs
│ [1] [go err] 2026/06/26 12:19:36 failed to prepare stream for session SAwrGESO3WOM (source=https://5d00db0e0fcd5.streamlock.net/7236/7236/playlist.m3u8): probe failed: ffprobe exited with error (code 1) at /usr/bin/ffprobe: exit status 1
│ [1] [go err] 2026/06/26 12:19:36 metadata probe failed for session SAwrGESO3WOM: ffprobe exited with error (code 1) at /usr/bin/ffprobe: exit status 1
│ [1] [go err] 2026/06/26 12:19:39 metadata probe failed for session SAwrGESO3WOM: ffprobe exited with error (code 1) at /usr/bin/ffprobe: exit status 1
@raffi/desktop electron:dev $ bun run server:build && cross-env NODE_ENV=development concurrently "bun run dev" "wait-on http://localhost:5173 && cross-env ELECTRON_DISABLE_SANDBOX=1 electron ."
│ [86 lines elided]
│ [1] [go err] 2026/06/26 12:19:36 failed to prepare stream for session SAwrGESO3WOM (source=https://5d00db0e0fcd5.streamlock.net/7236/7236/playlist.m3u8): probe failed: ffprobe exited with error (code 1) at /usr/bin/ffprobe: exit status 1
│ [1] [go err] 2026/06/26 12:19:36 metadata probe failed for session SAwrGESO3WOM: ffprobe exited with error (code 1) at /usr/bin/ffprobe: exit status 1
...
│ [1] [go err] 2026/06/26 12:36:55 failed to prepare stream for session WuysuOGJapWS (source=https://albportal.net/albkanalemusic.m3u8): probe failed: ffprobe exited with error (code 1) at /usr/bin/ffprobe: exit status 1

I also cannot seem to scroll in the channel guide anymore (maybe overflow is hidden?)

This dot here is not aligned (this is a really minor detail but it would be nice to have it properly centered with the line)
image

The timestamps do not have x padding
image

The selected group and stream should likely be saved so they're still selected when the user comes back to the page and don't reset back to all groups and the first stream.

Also as a side note, I feel like the channel guide could likely be done better, and maybe allow the user to choose whether to just place the channels in an icon grid (useful for when their list doesnt have EPG so staring at a blank row with just Live doesnt waste space), or maybe just default to the grid when theres no epg? you could experiment with this, but if you go the customization route make sure the customization is done in the preferences in a Live TV section in the settings!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/lib/iptv/cache.ts`:
- Around line 297-303: The `serializeGuide` function builds
`programmesByChannel` from a plain `{}`, which allows feed-controlled
`channelId` values like `__proto__` or `constructor` to hit special object slots
instead of normal keys. Change the `programmesByChannel` accumulator to use a
null-prototype object so arbitrary channel IDs are stored safely as data. Keep
the rest of the serialization logic unchanged, and make sure the
`StoredXmltvGuide` shape still receives the same entries from
`guide.programmesByChannel`.

In `@packages/app/src/pages/live/liveHelpers.ts`:
- Around line 26-30: The Live TV selection state is missing per-source
last-stream persistence, so the restored selection cannot bring back the
previously active channel. Update LiveTvSelection in liveHelpers.ts to include a
per-source last-channel field alongside sourceId, groupsBySourceId, and
favoritesBySourceId, then make sure the selection hydration/persistence path
reads and writes that field together with the existing state. Use the
LiveTvSelection type and the related load/save logic that consumes it to keep
the selected group and stream restored between visits.
- Around line 214-225: The channel filtering logic in getVisibleChannels should
treat any persisted group value that is not recognized as ALL_GROUPS, because
restored selectedGroup values can become stale when providers rename or remove
groups. Update the selection/group normalization path before or within
getVisibleChannels so unknown non-favorites groups fall back to ALL_GROUPS
instead of comparing against channel.group and filtering everything out. Use the
existing group checks in getVisibleChannels and the selectedGroup restoration
flow to ensure only valid group names are preserved.

In `@packages/app/src/pages/player/videoSession.ts`:
- Around line 559-560: The Hls error handler in videoSession should not log the
raw `data` object because it may expose sensitive IPTV request URLs and
credentials. Update the `hls.on(Hls.Events.ERROR, ...)` handler to log only
sanitized, non-sensitive fields from the hls.js error payload, and avoid
printing request context, full URLs, or nested objects that may contain secrets.
- Around line 570-575: The native HLS fallback branch in videoSession should not
return null because cleanupSession() needs a handle to clear startTimeout and
remove the native media listeners if the player unmounts early. Update the logic
around the videoElem.canPlayType("application/vnd.apple.mpegurl") path to return
a cleanup function or session handle that unregisters handleNativeReady from
loadedmetadata/canplay and clears any timeout, so stale player error state
cannot fire after navigation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bcb08de0-b65c-44a5-9c6d-8663e80b5158

📥 Commits

Reviewing files that changed from the base of the PR and between c6b8187 and b69f656.

📒 Files selected for processing (13)
  • packages/app/src/components/home/SearchBar.svelte
  • packages/app/src/lib/iptv/cache.test.ts
  • packages/app/src/lib/iptv/cache.ts
  • packages/app/src/pages/live/LiveTV.svelte
  • packages/app/src/pages/live/components/LiveChannelLogo.svelte
  • packages/app/src/pages/live/components/LiveGroupFilter.svelte
  • packages/app/src/pages/live/components/LiveGuide.svelte
  • packages/app/src/pages/live/components/LiveSourceManager.svelte
  • packages/app/src/pages/live/liveHelpers.test.ts
  • packages/app/src/pages/live/liveHelpers.ts
  • packages/app/src/pages/player/Player.svelte
  • packages/app/src/pages/player/playerSessionLoader.ts
  • packages/app/src/pages/player/videoSession.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/app/src/pages/live/components/LiveChannelLogo.svelte
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/app/src/pages/live/components/LiveSourceManager.svelte
  • packages/app/src/pages/live/LiveTV.svelte
  • packages/app/src/components/home/SearchBar.svelte

Comment thread packages/app/src/lib/iptv/cache.ts
Comment thread packages/app/src/pages/live/liveHelpers.ts
Comment thread packages/app/src/pages/live/liveHelpers.ts Outdated
Comment thread packages/app/src/pages/player/videoSession.ts Outdated
Comment thread packages/app/src/pages/player/videoSession.ts Outdated
@krissedout

krissedout commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Hello again:

  • Some streams load now, others either ffprobe fail or give me a manifestLoadError (most common) or a levelLoadError
  • the UI is definitely still rough, get rid of the entire container bg and header + footer there is no need for them, in the grid view you could make channels cards instead of squares as that would likely make more sense and overwhelm the user less, also you don't need to paginate, use virtualized lists that load as you go (with a buffer) instead which is the modern approach to this issue, and do not show a favorite button on every single channel, you could likely add a right click context menu similar to how Home does for the titles.
  • Favorites shouldn't have an icon as the other groups dont either, and you could add a searchbar at the top of the group dropdown which is autofocused so people can filter their groups quickly
  • As for the player, you likely want to get rid of the time and progress bar and clip button, and the subtitle and audio track selectors as it's not a movie and its a live stream so basically that just leaves the play/pause, zoom, fullscreen and volume in the player controls which can be a single row.
  • The sources modal should fade in and out the same as the Settings modal
  • Please address the coderabbit reviews

@cat5edopeHA

Copy link
Copy Markdown
Author

Can you give me the free playlist you have that is failing? I can’t reproduce the failure

@krissedout

krissedout commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Can you give me the free playlist you have that is failing? I can’t reproduce the failure

Sure, https://github.com/free-tv/iptv (the url is https://raw.githubusercontent.com/Free-TV/IPTV/master/playlist.m3u8)

Obviously it's likely horrible in terms of quality but VLC does handle it (most of it) so I think we should be able to too.

@krissedout

Copy link
Copy Markdown
Collaborator

Hi, I just wanted to check in on this PR, if you need any help please say so I can help implement it!

@cat5edopeHA

Copy link
Copy Markdown
Author

I’m still working on it, didn’t want to keep spamming updates

@cat5edopeHA

cat5edopeHA commented Jul 3, 2026

Copy link
Copy Markdown
Author

I dug into the Free-TV playlist playback failures a bit more, especially the random favorites that appear to fail before the player can do anything useful.

This looks like an upstream playlist/content issue rather than something this PR should try to special-case in Raffi.

A lot of entries in https://raw.githubusercontent.com/Free-TV/IPTV/master/playlist.m3u8 are not stable direct media streams:

  • Some entries are normal webpage URLs, e.g. YouTube/Dailymotion/channel pages, not HLS/DASH manifests.
  • Some manifest-looking URLs are currently dead (404) or blocked (403).
  • Some origins fail TLS/certificate validation.
  • A few sampled direct HLS streams do work, e.g. A2 CNN Albania and Bloomberg.

VLC is not a perfect comparison here because it has a much broader media stack and can sometimes resolve or tolerate things Raffi intentionally does not handle yet: webpage-like entries, redirects, odd TLS/server behavior, broken MIME types, and other provider quirks. Raffi's Live TV path is currently expecting a direct playable stream URL/manifest, not a general-purpose web video extractor.

@krissedout

Copy link
Copy Markdown
Collaborator

Alright that's okay then you can ignore it, you should still address the remaining items:

  • the UI is definitely still rough, get rid of the entire container bg and header + footer there is no need for them, in the grid view you could make channels cards instead of squares as that would likely make more sense and overwhelm the user less, also you don't need to paginate, use virtualized lists that load as you go (with a buffer) instead which is the modern approach to this issue, and do not show a favorite button on every single channel, you could likely add a right click context menu similar to how Home does for the titles.
  • Favorites shouldn't have an icon as the other groups dont either, and you could add a searchbar at the top of the group dropdown which is autofocused so people can filter their groups quickly
  • As for the player, you likely want to get rid of the time and progress bar and clip button, and the subtitle and audio track selectors as it's not a movie and its a live stream so basically that just leaves the play/pause, zoom, fullscreen and volume in the player controls which can be a single row.
  • The sources modal should fade in and out the same as the Settings modal
  • Please address the coderabbit reviews

@cat5edopeHA

Copy link
Copy Markdown
Author

CodeRabbit review-body items are handled in current head 829e795 as well:

  • Xtream channel caching: Xtream refresh results are not persisted; the cache path removes any legacy Xtream cache and returns null for Xtream sources.
  • Source-manager keyboard handling: the dialog now focuses itself on mount, so Escape keydown reaches the modal/backdrop close path while the close button remains tabbable.
  • Source switching stale search: hydrateStoredSource() clears searchQuery whenever the loaded source changes, so disabled/old searches do not carry into the next source.
  • Review nit tests: malformed XMLTV programme timestamps and M3U group id collisions are both covered by targeted tests.

Validation run on the pushed head: bun test packages/app/src/lib/iptv/*.test.ts packages/app/src/pages/live/liveHelpers.test.ts (50 pass / 0 fail), bun run check, bun run build:desktop, and clean git diff --check for both the PR range and upstream range.

@krissedout

Copy link
Copy Markdown
Collaborator

Hello, sorry for the delay, this seems much better, a few new issues:

  • The live player controls container doesnt match the style of the normal one, specifically the play/pause button which should use our icon from the normal player controls, and also you need to add additional right x padding on the live controls to keep it symmetrical to the eye as the volume slider is not the same as the control buttons
image
  • There's still +300 lines of IPTV code in the electron mainIpc.cjs file, IPTV code should not be in top level files unless necessary to extend them but the code should be extracted to proper files/folders as much as possible.
  • Left and right arrow keys still provide feedback information (+5 -5 seeks) even though they dont actually seek, remove their functionality in the live player
  • There's a LOT of tests and test files even though raffi doesn't even have an existing test suite, I'd say most of these tests are redundant and you can safely remove them, you can keep any important ones for example functionality or security, but this is generally a known agent issue where it makes a ton of unnecessary tests, especially regression tests after it removes something etc.

Other than that it's looking pretty good so far!

@krissedout

krissedout commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

This is looking much better overall! A couple things I’d still change and check before merging:

LiveSourceManager.svelte uses window.confirm directly when deleting an IPTV source. Since this is a destructive action and Raffi already has confirmDialog(...) / Electron dialog plumbing, can we route source deletion through that instead? That keeps desktop using the native app dialog and avoids introducing default browser UI here.

Also, I don’t think the Live TV page should visually select the last played channel as soon as you open it. That feels a bit odd because the user hasn’t actually clicked/started that channel in the current visit yet. Remembering the last channel is useful, but maybe only use it for resume/open behavior if we add that later, not as the active selected state on page load.

One question too: Xtream is exposed as a first-class source type, but the cache layer intentionally does not persist Xtream refresh results. Is that intentional? If Xtream is meant to ship in this PR, I’d like to make sure we’re okay with those sources not restoring refreshed channel/guide state between visits the way M3U sources do.

After these are resolved, I think we can merge!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Native IPTV source support with M3U/Xtream and XMLTV EPG

2 participants