Live TV channels in Xtream playlists go blank whenever the provider's
get_short_epg returns nothing, even when the user has working XMLTV
URLs in Settings. The XMLTV pipeline already populates epg_programs
but only the M3U module was reading it.
Wire those uploads up as a fallback for Xtream too: when the provider
returns nothing for a channel with an epg_channel_id, look it up in
the local table. A settings toggle flips the priority for users whose
curated XMLTV is better than the provider's auto guide.
EpgQueueService.enqueue() became async to batch the XMLTV lookup once
per viewport change. To keep the queue consistent under fast scroll,
all shared-state mutations now happen behind a generation counter so
only the latest call commits. Per-method bridge gating in the new
XtreamXmltvFallbackService keeps each path working when the preload
exposes only one of the two endpoints.
VLC was unconditionally spawned per click — VLC's own single-instance
preference fails because the per-launch RC args defeat its D-Bus
forwarder. Mirror the existing MPV reuse pattern so users can opt in to
driving one tracked VLC via its RC interface (clear + add) instead of
opening a new window every stream.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f957cd9849e0
Three first-paint fixes for cold app start.
#1 — Inline splash in index.html
Cold start used to show a blank Material-grey background until the
~1.5MB preloaded chunk + 300KB styles.css downloaded and Angular
bootstrapped. On slow disks/networks that's 1-3+ s of "is the app
frozen?" before any pixel of UI appears.
Add a self-contained splash (inline CSS, no extra HTTP, no assets) —
"IPTVnator" wordmark + a CSS spinner — that paints immediately from
the parsed HTML. Removed in main.ts after bootstrapApplication()
resolves, gated on requestAnimationFrame so the swap happens after
AppComponent's first paint (no flash of empty background between
splash removal and the real UI).
Respects prefers-reduced-motion (spinner stops, opacity dims).
#3 — Drop legacy `global = window` polyfill
The inline <script> in <head> ("if (global === undefined) var global =
window") was a workaround for an older library expecting Node's
`global`. Verified nothing in the current bundle reads window.global
(the only matches are TypeScript `declare global { ... }` blocks and
unrelated route paths). Removes one render-blocking inline <script>
parse from the critical path.
#5 — Synchronous language hint
Settings live in IndexedDB (via @ngx-pwa/local-storage), which the
TranslateModule cannot read synchronously at bootstrap. So the first
render historically used English regardless of the user's preference,
then re-rendered every TranslatePipe in the tree once initSettings()
finished — visible flash for non-English users.
Mirror the active language to localStorage in initSettings() once the
IDB-backed settings resolve. Add getInitialLanguage() in app.config.ts
that reads that key synchronously before bootstrap and passes it to
TranslateModule.forRoot({ defaultLanguage }). First-ever boot still
falls back to English (no hint yet); every subsequent boot renders in
the saved language from the very first frame.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f957cd9849e0
Both ElectronService.listenOn() and PwaService.listenOn() called
window.addEventListener('message', callback) but neither could remove
the listener afterwards:
- ElectronService.removeAllListeners() called a placeholder
getListenerForCommand() that returned a fresh () => undefined function
on every invocation — never matching the registered listener (with a
comment confessing as much).
- PwaService.removeAllListeners() was a literal `// not implemented`
no-op.
The result was a latent memory leak: every listenOn() call accumulated
a global window listener that nothing could remove. Calling listenOn()
twice for the same command also stacked duplicate listeners.
Track callbacks in a messageListeners Map keyed by command name. On
listenOn(), drop any existing listener for that command before adding
the new one. On removeAllListeners(type), remove either the named
listener or all of them (when type === 'all').
The fix lands proactively: grep finds no current callers in the
renderer, so today's leak is theoretical. But the API is exposed via
the abstract DataService and the placeholder comment explicitly invited
the bug. Closing the footgun is cheaper than discovering it later.
Inspired by matracey/iptvnator@df7e1dc — extends the fix to PwaService,
which had the same bug in even more obvious form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 3f453cc0085a
Use Map and Set lookups for M3U favorite aggregation, dashboard global favorite mapping, and channel-list favorite/group resolution.
Remove the duplicate NgxIndexedDB provider registration and add regression coverage for duplicate URL and earliest-match behavior.
Entire-Checkpoint: c2cea9c530e6
- Introduced tooling for building and staging the macOS `libmpv` runtime for IPTVnator's embedded MPV player.
- Added `build-macos-runtime.mjs` for building an LGPL-compatible runtime from source.
- Created `stage-macos-runtime.mjs` for staging the built runtime artifacts.
- Implemented validation for the packaged embedded MPV runtime in `electron-after-pack.cjs` and `embedded-mpv-macos.cjs`.
- Updated packaging scripts to ensure the embedded MPV runtime is correctly integrated and validated during the build process.
- Added README files to document the expected layout and usage for the embedded MPV runtime artifacts.
Entire-Checkpoint: c6e522b4276c
- Expanded EmptyStateType to include 'welcome-dashboard', 'welcome-sources', 'no-data'.
- Introduced FeatureCard and SourceCard interfaces for better structure.
- Added FEATURE_CARDS and SOURCE_CARDS constants for feature and source card configurations.
- Updated empty-state.component.ts to handle new types and actions.
- Created responsive styles in empty-state.responsive.scss for better mobile support.
- Added theme styles in empty-state.themes.scss for light mode.
- Developed specific styles for welcome dashboard and sources in empty-state.welcome-dashboard.scss and empty-state.welcome-sources.scss.
- Updated recent-playlists.component.html to utilize new empty state types.
- Modified recent-playlists.component.ts to support new playlist actions.
- Introduced PlaylistFileImportService for handling playlist file imports.
- Updated downloads.component.html and downloads.component.ts to integrate new empty state handling.
- Enhanced unified-collection-page.component.html and unified-collection-page.component.ts to utilize empty state for no results.
Entire-Checkpoint: c6e522b4276c
- Updated confirmation dialog messages to provide detailed consequences of deleting playlists.
- Added backup hints and progress messages for better user experience during playlist deletion.
- Ensured consistency in language across all supported translations (de, el, en, es, fr, it, ja, ko, nl, pl, pt, ru, tr, zh, zhtw).
Entire-Checkpoint: c6e522b4276c
- Introduced StalkerCollectionDetailComponent to handle detailed views for Stalker collections.
- Added XtreamCollectionDetailComponent for managing Xtream collection details.
- Updated routing components to integrate new detail components for both Stalker and Xtream.
- Enhanced global favorites and recently viewed functionalities to support new detail navigation for Stalker and Xtream items.
- Improved styling and structure for better responsiveness and user experience in detail views.
Entire-Checkpoint: c6e522b4276c
- Set default appearance for mat-form-field to 'outline' and dynamic subscript sizing in app.config.ts.
- Adjust dialog dimensions in workspace-shell-actions.service.ts for better responsiveness.
- Refactor settings.component.scss to remove specific mat-form-field font size overrides.
- Enhance global styles in m3-theme.scss for outlined inputs, ensuring consistency across the application.
- Modify workspace-command-palette styles to improve visual integration with the overall theme.
- Revamp add-playlist-dialog component to utilize segmented controls for category selection, improving UX.
- Update file-upload component to provide better feedback on file selection and drag-and-drop interactions.
- Clean up text and URL upload components by removing unnecessary placeholders and improving layout.
- Refine stalker-portal-import component by removing placeholder text for clarity.
Entire-Checkpoint: c6e522b4276c
- Implemented a loading state for channel lists in various components, enhancing user experience during data fetching.
- Added `ChannelListLoadingStateComponent` to display loading skeletons for channels and groups.
- Updated `M3uWorkspaceRouteSession` to manage loading states more effectively when fetching playlists.
- Modified `VideoPlayerComponent`, `SidebarComponent`, and `ChannelListContainerComponent` to utilize the new loading state.
- Enhanced tests to cover new loading behaviors and ensure proper dispatching of loading actions.
- Replaced moment.js usage with Intl.DateTimeFormat for date formatting in various components.
- Introduced utility functions `normalizeDateLocale` and `formatWithIntl` for locale normalization and date formatting.
- Updated PlaylistSwitcher, RecentPlaylists, ContentCard, EPG components, and MultiEpgContainer to utilize the new date formatting approach.
- Removed moment-date pipe and related tests, simplifying date handling logic.
- Ensured all date displays respect the current locale settings.
Entire-Checkpoint: 271bd0162027