From 0643acac3a6cb050f1d1d121755de3fc98560534 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 1 Feb 2026 10:55:45 +0100 Subject: [PATCH] feat: add o_name support and Stalker playback position tracking - Add o_name (original title) field to Stalker VOD interfaces - Prefer o_name over name in adapters and grid list display - Add playback position tracking for Stalker VOD content - Support resume playback from last position in vod-details - Add real-time position updates via IPC listener - Add progress indicators for Stalker VOD items in grid view - Add debug logging for VOD series seasons resource Co-Authored-By: Claude Opus 4.5 --- .../database/playback-position.events.ts | 48 ++-- .../app/events/database/playlist.events.ts | 4 + .../grid-list/grid-list.component.ts | 2 +- .../recently-viewed.component.ts | 5 +- .../stalker-favorites.component.ts | 5 +- .../stalker-main-container.component.html | 4 +- .../stalker-main-container.component.ts | 24 +- apps/web/src/app/stalker/stalker.store.ts | 61 ++++- .../category-content-view.component.html | 4 +- .../category-content-view.component.ts | 210 +++++++++++++++--- .../category-view/category-view.component.ts | 3 +- .../with-playback-positions.feature.ts | 42 +--- .../vod-details/vod-details.component.ts | 7 +- libs/m3u-state/src/lib/effects.ts | 12 +- .../src/lib/stalker-vod-details.interface.ts | 4 +- .../src/lib/vod-details-adapters.ts | 3 +- 16 files changed, 329 insertions(+), 109 deletions(-) diff --git a/apps/electron-backend/src/app/events/database/playback-position.events.ts b/apps/electron-backend/src/app/events/database/playback-position.events.ts index 8e85e2d92..eeeb03b99 100644 --- a/apps/electron-backend/src/app/events/database/playback-position.events.ts +++ b/apps/electron-backend/src/app/events/database/playback-position.events.ts @@ -8,6 +8,31 @@ import { ipcMain } from 'electron'; import { getDatabase } from '../../database/connection'; import * as schema from '../../database/schema'; +/** + * Ensure playlist exists in SQLite (auto-create if missing) + * This handles legacy playlists that weren't synced to SQLite + */ +async function ensurePlaylistExists( + db: any, + playlistId: string, + playlistType: 'xtream' | 'stalker' | 'm3u-file' | 'm3u-text' | 'm3u-url' = 'stalker' +): Promise { + const existing = await db + .select() + .from(schema.playlists) + .where(eq(schema.playlists.id, playlistId)) + .limit(1); + + if (existing.length === 0) { + // Create a minimal placeholder entry for the playlist + await db.insert(schema.playlists).values({ + id: playlistId, + name: 'Imported Playlist', + type: playlistType, + }); + } +} + /** * Save/update playback position for content */ @@ -15,13 +40,11 @@ ipcMain.handle( 'DB_SAVE_PLAYBACK_POSITION', async (event, playlistId: string, data: any) => { try { - console.log( - '[DatabaseEvents] Saving playback position:', - playlistId, - data - ); const db = await getDatabase(); + // Ensure playlist exists (handles legacy playlists not in SQLite) + await ensurePlaylistExists(db, playlistId, data.playlistType); + // Prepare values const values = { playlistId, @@ -83,9 +106,6 @@ ipcMain.handle( contentType: 'vod' | 'episode' ) => { try { - console.log( - `[DatabaseEvents] Getting playback position: playlistId=${playlistId}, id=${contentXtreamId}, type=${contentType}` - ); const db = await getDatabase(); const result = await db .select() @@ -117,9 +137,6 @@ ipcMain.handle( 'DB_GET_SERIES_PLAYBACK_POSITIONS', async (event, playlistId: string, seriesXtreamId: number) => { try { - console.log( - `[DatabaseEvents] Getting series playback positions: playlistId=${playlistId}, seriesXtreamId=${seriesXtreamId}` - ); const db = await getDatabase(); const result = await db .select() @@ -135,15 +152,6 @@ ipcMain.handle( ) ); - console.log( - `[DatabaseEvents] Found ${result.length} positions for series ${seriesXtreamId}:`, - result.map((r) => ({ - contentXtreamId: r.contentXtreamId, - seriesXtreamId: r.seriesXtreamId, - position: r.positionSeconds, - })) - ); - return result; } catch (error) { console.error('Error getting series playback positions:', error); diff --git a/apps/electron-backend/src/app/events/database/playlist.events.ts b/apps/electron-backend/src/app/events/database/playlist.events.ts index bb5f54d71..190fc1050 100644 --- a/apps/electron-backend/src/app/events/database/playlist.events.ts +++ b/apps/electron-backend/src/app/events/database/playlist.events.ts @@ -21,6 +21,8 @@ ipcMain.handle( serverUrl?: string; username?: string; password?: string; + macAddress?: string; + url?: string; type: string; } ) => { @@ -32,6 +34,8 @@ ipcMain.handle( serverUrl: playlist.serverUrl, username: playlist.username, password: playlist.password, + macAddress: playlist.macAddress, + url: playlist.url, // enforce supported types type: playlist.type as | 'xtream' diff --git a/apps/web/src/app/shared/components/grid-list/grid-list.component.ts b/apps/web/src/app/shared/components/grid-list/grid-list.component.ts index 380de187a..131c78d4b 100644 --- a/apps/web/src/app/shared/components/grid-list/grid-list.component.ts +++ b/apps/web/src/app/shared/components/grid-list/grid-list.component.ts @@ -59,7 +59,7 @@ import { WatchedBadgeComponent } from '../../../xtream-tauri/shared/watched-badg star{{ rating }} } - @let title = i.title ?? i.name; + @let title = i.title ?? i.o_name ?? i.name;
{{ title || 'No name' }} diff --git a/apps/web/src/app/stalker/recently-viewed/recently-viewed.component.ts b/apps/web/src/app/stalker/recently-viewed/recently-viewed.component.ts index 778fe8fcb..dcd4f585b 100644 --- a/apps/web/src/app/stalker/recently-viewed/recently-viewed.component.ts +++ b/apps/web/src/app/stalker/recently-viewed/recently-viewed.component.ts @@ -131,7 +131,7 @@ export class RecentlyViewedComponent { this.stalkerStore.setSelectedContentType(item.category_id); switch (item.category_id) { case 'itv': - this.createLinkToPlayVodItv(item.cmd, item.name, item.logo); + this.createLinkToPlayVodItv(item.cmd, item.o_name || item.name, item.logo); break; case 'vod': // Normalize the item to ensure is_series flag is properly set @@ -229,7 +229,8 @@ export class RecentlyViewedComponent { // Ensure info object exists const info = item.info || { - name: item.name || item.title || 'Unknown', + name: item.o_name || item.name || item.title || 'Unknown', + o_name: item.o_name, movie_image: item.cover || item.screenshot_uri || item.logo, description: item.description, }; diff --git a/apps/web/src/app/stalker/stalker-favorites/stalker-favorites.component.ts b/apps/web/src/app/stalker/stalker-favorites/stalker-favorites.component.ts index ca0ee8013..29187559c 100644 --- a/apps/web/src/app/stalker/stalker-favorites/stalker-favorites.component.ts +++ b/apps/web/src/app/stalker/stalker-favorites/stalker-favorites.component.ts @@ -134,7 +134,7 @@ export class StalkerFavoritesComponent { switch (item.category_id) { case 'itv': this.stalkerStore.setSelectedContentType('itv'); - this.createLinkToPlayVodItv(item.cmd, item.name, item.logo); + this.createLinkToPlayVodItv(item.cmd, item.o_name || item.name, item.logo); break; case 'vod': this.itemDetails = normalizedItem; @@ -202,7 +202,8 @@ export class StalkerFavoritesComponent { const source = item.details || item; const info = source.info || { - name: item.name || item.title || 'Unknown', + name: item.o_name || item.name || item.title || 'Unknown', + o_name: item.o_name, movie_image: item.cover || item.screenshot_uri || item.logo, description: item.description, releasedate: item.releasedate || item.year, diff --git a/apps/web/src/app/stalker/stalker-main-container.component.html b/apps/web/src/app/stalker/stalker-main-container.component.html index c962d1ea3..3bfd43f62 100644 --- a/apps/web/src/app/stalker/stalker-main-container.component.html +++ b/apps/web/src/app/stalker/stalker-main-container.component.html @@ -38,7 +38,7 @@ " alt="logo" /> - {{ item.name }} + {{ item.o_name || item.name }}