Favorites and recently-viewed rows store Stalker items as full JSON
snapshots, so a vclub-style embedded series[] episode list froze at the
moment the row was written: a series favorited when only episode 1 was out
kept showing one episode forever when opened from favorites, recents,
Continue Watching, or any dashboard rail.
New withStalkerSnapshotRefresh() store feature renders the stored snapshot
immediately and re-fetches the item from the portal in the background via a
title search (get_ordered_list&type=vod&search=..., matched by id, paginated
up to 5 pages, wildcard-category retry), patching fresh episodes and cmd into
the active selection. The patch is guarded on both the item id and the active
playlist id, since Stalker ids are only unique per portal.
Only the in-memory selection is patched — the stored snapshot row is
deliberately left alone, because every entry path into the detail view runs
this refresh and writing it back would add an uncontrolled background writer
to the whole-playlist read-modify-write that every favorite/recent mutation
performs.
Also fixes the stalker-mock-server embedded-series scenario, which generated
series[] as objects the app's vclub adapters filter out instead of the
episode-number arrays real portals send.
Regular type=series and Ministra is_series items are unaffected; Xtream is
unaffected (get_series_info is never cached).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(m3u): extract ClearKey DRM from #KODIPROP playlist lines
Adds the typed ChannelDrm model (shared interfaces) and a KODIPROP
post-processing step in createPlaylistObject() — the single funnel for all
four playlist import paths. Parses inputstream.adaptive.license_type,
license_key and drm_legacy; ClearKey keys accepted as kid:key hex pairs,
W3C ClearKey license JSON, or a plain kid→key JSON map. Unsupported license
types (Widevine/PlayReady/license URLs) are preserved with supported=false
so playback can surface a DRM diagnostic instead of failing silently.
Also adds isDashStreamUrl/isDashChannel helpers for DASH routing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(playback): add Shaka DASH source engine with ClearKey support
Introduces ShakaVideoSession (libs/ui/playback/src/lib/shaka-engine/): a
lazily imported shaka-player engine (separate lazy chunk, ~217 KB transfer)
owning attach/configure/load with an operation queue and generation guard
against channel-switch races. Channel ClearKey config maps to
drm.clearKeys; channels with an unsupported license type emit a
DrmOrEncryption diagnostic without starting an engine. Shaka errors are
classified into the existing playback diagnostics
(PlaybackDiagnosticSource.Shaka).
Wires the engine into both built-in players like hls.js/mpegts.js:
- HTML5: extension === 'mpd' branch in playChannel(); hls/mpegts/native
glue extracted to helpers to keep the component within the size budget
- ArtPlayer: customType 'mpd' in ArtPlayerSourceSession (+ getDrm seam)
- Shared controls: WebVideoControlsSource kind 'shaka' +
WebVideoShakaControls using the Shaka 5 text model (selectTextTrack(null)
hides subtitles; Player.setTextTrackVisibility no longer exists)
Adds a CJS shaka-player jest stub (video.js precedent) for web specs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(m3u): route DASH channels to the inline Shaka-capable player
DASH (.mpd) channels always play in a built-in web engine (radio
precedent): external MPV/VLC cannot receive KODIPROP ClearKey
configuration (VLC upstream #29465) and Video.js has no DASH bridge yet.
- shouldShowInlinePlayer() bypasses the external-player setting for DASH
- new shouldAutoLaunchExternalPlayer() guard consolidates the MPV/VLC
auto-launch conditions in the m3u-state effects (incl. catch-up path)
- the M3U page overrides the player for DASH channels: ArtPlayer stays
ArtPlayer, everything else falls back to the HTML5 player
- ChannelDrm is passed through ResolvedPortalPlayback into the synthetic
player-view channel
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(e2e): add offline DASH ClearKey fixtures and e2e coverage
Fixtures (apps/web-e2e/src/fixtures/dash/): ~4s VP9+Opus DASH, clear and
CENC-encrypted variants with fixed synthetic ClearKey credentials.
Content synthesized by ffmpeg; encryption done by Shaka Packager because
ffmpeg's mp4 muxer writes senc-only metadata (Chromium needs saiz/saio)
and cannot produce the subsample encryption the VP9 CENC binding
requires. Generation script + README document regeneration.
web-e2e (Chromium): import an M3U with KODIPROP ClearKey via raw text,
verify encrypted and clear DASH actually play (currentTime advances, no
diagnostic banner) and that an unsupported license type (Widevine)
surfaces the DRM diagnostic. Fixtures are served through Playwright route
interception with HTTP Range support; the Angular service worker is
blocked since SW-routed requests bypass interception.
electron-backend-e2e: the same happy path + negative against a local
Range-aware fixture server — the automated proof that ClearKey EME works
in the real Electron runtime (file:// secure context).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: document DASH + ClearKey playback architecture
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pwa): extract KODIPROP DRM on the web-backend /parse import path
The web-backend keeps its own playlist builder for the PWA URL-import
path, so the shared createPlaylistObject() DRM hook never ran there and
encrypted DASH channels imported by URL reached Shaka without keys.
Apply extractDrmFromRaw() in that builder too and cover the path with a
regression test.
Addresses Codex review on PR #1225.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(playback): interrupt stalled Shaka loads and destroy failed engines
Two review findings on the ShakaVideoSession lifecycle:
- stop()/start() now tear the current player down immediately instead of
queueing the destroy behind the in-flight operation. Shaka's destroy()
interrupts a pending load() (LOAD_INTERRUPTED), so a stalled manifest
fetch can no longer wedge the operation chain and block the next
channel start (Codex P1).
- A rejected attach()/load() now destroys the failed player after
emitting the diagnostic, so a non-functional engine never stays
attached to the media element or exposed to the shared-controls
bridge (Greptile P1).
Regression tests cover both paths. The Shaka fakes are consolidated into
a shared jest-free test double that mirrors the destroy-interrupts-load
semantic, and the ArtPlayer source-session spec is split (fixtures +
DASH cases) to stay within the max-lines lint budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(m3u): unify DASH URL detection with playback extension normalization
isDashStreamUrl() used the simpler getStreamExtensionFromUrl(), so URLs
the player engines classify as DASH (stream.MPD, ?ext=mpd, ?format=mpd)
were not routed to the Shaka-capable inline player and lost their
ClearKey metadata with Video.js or external players configured
(Codex P2). The normalized getPlaybackMediaExtensionFromUrl() now lives
in @iptvnator/shared/m3u-utils (re-exported unchanged from the playback
lib) and both routing and engine selection share it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(lint): satisfy CI lint and CodeQL in DASH support files
- replace shell-built tar/npm commands with execFileSync arg arrays in
the fixture generator (CodeQL: uncontrolled shell command)
- give jest stub methods explicit bodies (no-empty-function)
- compact the diagnostic label switches in WebPlayerViewComponent to
stay under the max-lines budget
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(playback): tear down the Shaka engine on critical error events too
A non-recoverable Shaka error emitted after a successful load left the
dead engine attached to the media element and exposed to the
shared-controls bridge (Greptile P1, round 2). Critical error events now
destroy the player right after the diagnostic is emitted, matching the
load-failure path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(m3u): honor DASH catch-up URLs and drop unusable DRM fallbacks
Two Codex round-2 findings:
- The inline-playback DASH gate only examined the channel URL, while the
external-player guard checks the resolved catch-up URL — a replay that
resolves to an .mpd manifest with MPV/VLC configured ended up with no
player at all. The gate now uses the effective playback URL
(activePlaybackUrl ?? channel.url).
- The unsupported-DRM diagnostic advertised MPV/VLC fallback actions,
but external players cannot receive the KODIPROP license config either
— the diagnostic no longer recommends them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(playback): suppress unusable external fallback for ClearKey DRM failures
Runtime DRM errors on channels that carry KODIPROP ClearKey config (wrong
or rotated keys) advertised MPV/VLC fallback actions, but external
players never receive the license config — the fallback could only fail
differently. DRM-classified diagnostics from such channels no longer
recommend external players; clear channels keep the hint.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(m3u): symmetric DASH inline gate and lazy DRM for pre-upgrade playlists
- The inline DASH gate is now true when either the channel or the
resolved catch-up URL is DASH, mirroring the external-player guard —
a .mpd channel whose catch-up resolves to .m3u8 no longer ends up
with no player at all.
- Playlists imported before the DRM feature carry no drm field, but the
raw KODIPROP block survived in the stored items; the M3U page now
falls back to extractDrmFromRaw(channel.raw) at playback time, so
encrypted channels work without a re-import (Channel gains raw?).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: sync the DASH/Shaka contract across agent docs
Mirrors the DASH/Shaka source-engine contract into AGENTS.md and adds
Shaka to the shared web-video bridge descriptions in CLAUDE.md and the
player-controls contract; documents the lazy raw-KODIPROP DRM fallback
for pre-upgrade playlists in the M3U architecture doc.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(playback): reset the media element for rejected DRM and widen ClearKey fallback suppression
- Switching from a playing stream to an unsupported-DRM DASH channel
loads no new source, but play() still ran and the un-loaded element
could resume the previous stream underneath the diagnostic banner.
The HTML5 player now resets the element instead of playing.
- Any inline failure on a KODIPROP ClearKey channel (manifest, codec,
media, network — not just DRM-category errors) is unsolvable in
MPV/VLC, which never receive the license config; the external
fallback hint is now suppressed for all diagnostics of such channels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(playback): restore suppressed DASH captions when the preference re-enables
The Shaka bridge dropped the auto-selected text track with
selectTextTrack(null) when showCaptions was off, but did not remember it
— re-enabling the preference mid-session left captions permanently off
(HLS/native bridges already restore). The session now remembers the
suppressed track id and reselects it via the bridge's caption-state pass;
suppression is also skipped when no track is active. Covered by session
and new WebVideoShakaControls specs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: retrigger CI
GitHub Actions created no check suites for the last three pushes to this
branch (third-party apps received the webhooks); an empty commit re-fires
the push and pull_request events.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(playback): split oversized Shaka session and HTML5 spec files
CI lint enforces max-lines 400: extract ShakaTextTrackSuppression and the
shaka-error helpers out of ShakaVideoSession, and move the DASH-specific
HTML5 player test into its own spec. No behavior change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci: allow manual dispatch of the cross-platform E2E workflow
GitHub stopped delivering push/pull_request events for this branch;
workflow_dispatch provides a manual escape hatch (CI and build-and-make
already have one).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Load the complete ITV channel list once per portal (Ministra get_all_channels with a paged crawl fallback) and use it for: full local search across every channel, per-genre category count badges, an all-channels paginated grid on Live TV entry, and eager bulk EPG so row previews appear without playing a channel. Censored (adult) genres absent from get_all_channels fall back to the legacy paged flow and show no badge. Includes mock-server support, unit + E2E coverage, and architecture docs.
Replaces the vertical EPG list with a shared horizontal `app-epg-timeline`
ribbon across all live surfaces (M3U player, unified live tab, Xtream, Stalker):
zoom, day navigation, short-programme grouping, catch-up/timeshift, and
per-state empty views. Backend gains timezone-aware `datetime()` comparisons,
unscoped source fallback, non-ASCII candidate matching, and chunked candidate
queries.
Timeline split into reusable, view-agnostic modules (archive/summary/dialog
service/render util/scroll controller) for the future EPG list view.
Fixes landed during review:
- honor the controlled `selectedDate` input (seed via linkedSignal)
- restore ribbon position across collapse/expand
- keep the ribbon mounted when scrolling across a gap day
- don't trigger block playback on Enter from nested watch/info buttons
- don't reset timeshift playback on the 30s now-tick during EPG gaps
Greptile 5/5 (safe to merge); Codex clean; CI green.
- save Xtream playlist details through browser-safe metadata persistence in PWA\n- keep PWA Xtream data source cache in sync with current playlist metadata\n- cover dialog close timing, stale cache, and PWA data-source bootstrap regression
- merge origin/master into PR #964 and keep embedded MPV test on the isolated playback sub-entrypoint
- centralize EPG capability through DataService.supportsEpg and update PWA web-e2e expectations
- split BrowserAccessError copy between Electron and PWA diagnostics
The redesign rollup in this branch replaced the Add Playlist dialog's
2-level type × subtype tabs with a single flat 5-card radiogroup, and
replaced the Settings theme list with a compact segmented control whose
options are just "Light"/"Dark"/"System" (no "theme" suffix). E2E tests
that pinned to the old roles/labels failed in CI:
- basic / xtream / stalker / self-hosted: getByRole('tab', ...) on
"Add via file upload" / "Xtream" / "Stalker" — there are no tabs in
the new dialog. Switched to getByRole('radio', { name: /M3U file/i })
and friends, matched against the new radio labels.
- settings: getByRole('radio', { name: 'System theme' }) — the new
picker uses just "System". Scoped to the [data-test-id="select-theme"]
radiogroup so it doesn't collide with the identically-labelled
cover-size options below.
The Electron `clickDialogSegmentedOption` helper grew a radio-role lookup
as its primary path and keeps the old tab/button/legacy-selector
fallbacks so a future redesign won't break every fixture again. The
"M3U" parent category becomes a no-op (the new picker has no standalone
M3U tile — callers always specialise to M3U URL/file/text immediately
after, which is what we want anyway).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Implemented unit tests for StalkerCollectionChannelsListComponent to verify EPG program loading and state management.
- Created unit tests for StalkerLiveStreamLayoutComponent to ensure proper rendering of EPG lists and handling of channel playback.
- Refactored EPG loading logic in StalkerLiveStreamLayoutComponent to improve performance and maintainability.
- Updated HTML template in StalkerLiveStreamLayoutComponent to replace deprecated components with new EPG list component.
- Enhanced EPG program handling by introducing methods for timestamp parsing and current program detection.
mat-button-toggle elements inside a group render their internal buttons
with role="radio", not role="button", so getByRole('button') selectors
fail. Switch to attribute selectors targeting the value attribute instead.
https://claude.ai/code/session_01VU6ZZTG7YrhKhKBQvBfmpp
- Implemented tests for tracking recently viewed channels in M3U playlists, ensuring they persist across app restarts.
- Added functionality to manage Xtream and Stalker history, including support for clearing recent items.
- Enhanced settings tests to verify persistence of user preferences across app restarts, including language, video player, and theme settings.
- Created comprehensive tests for managing sources, including filtering, sorting, editing, and deleting M3U, Xtream, and Stalker sources.
- Introduced mock fixtures for Xtream and Stalker categories to facilitate testing.
- Implemented tests for importing M3U playlists via native dialog.
- Added smoke tests for loading Xtream and Stalker content through IPC.
- Created tests to verify persistence of remote control settings across app restarts.
- Updated smoke tests to check for main window properties and workspace content rendering.
refactor(electron-backend): enhance store service and database path handling
- Modified store service to use a dynamic configuration directory based on environment.
- Refactored database connection to utilize new path utility functions for better path management.
fix(e2e): improve test selectors and structure for better reliability
- Updated test selectors in web-e2e tests to use role-based queries for better accessibility.
- Refactored settings tests to reduce redundancy and improve clarity.
chore(e2e): update TypeScript configuration for better inclusion of test files
- Adjusted tsconfig.json to ensure proper inclusion of e2e test files.
Entire-Checkpoint: bb9a8e2e351e
- Introduced `stalker-mock-server` with TypeScript configuration for local development and testing.
- Updated `playwright.config.ts` to run both Angular app and mock server in parallel during e2e tests.
- Created comprehensive e2e tests for Stalker portal functionality, including health checks, portal addition, and content loading.
- Added detailed architecture documentation for both `stalker-mock-server` and `xtream-mock-server`, outlining design decisions, data flow, and API protocols.
- Implemented `xtream-mock-server` to simulate Xtream Codes API for local development and testing, with corresponding e2e tests.
- Enhanced test isolation by resetting mock server state before each test run.