diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 98c4cd4e1..54bd330b6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -18,7 +18,14 @@ "Bash(npx tsc:*)", "Bash(find:*)", "Bash(xargs sed:*)", - "Skill(frontend-design)" + "Skill(frontend-design)", + "mcp__angular-cli__get_best_practices", + "mcp__nx-mcp__nx_workspace", + "Bash(grep:*)", + "Bash(npx nx lint:*)", + "Bash(npx nx build:*)", + "Bash(npx eslint:*)", + "Bash(npx nx run:*)" ], "deny": [], "ask": [] diff --git a/README.md b/README.md index decbf3e32..c7588e248 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ GitHub Workflow Status Releases Codecov Telegram Bluesky

-Telegram channel for discussions +Telegram channel for discussions | Buy me a coffee | GitHub Sponsors **IPTVnator** is a video player application that provides support for IPTV playlist playback (m3u, m3u8). The application allows users to import playlists using remote URLs or by uploading files from the local file system. Additionally, it supports EPG information in XMLTV format which can be provided via URL. 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 423458de0..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() 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/home/video-player/video-player.component.html b/apps/web/src/app/home/video-player/video-player.component.html index 5926a7050..66f90c69e 100644 --- a/apps/web/src/app/home/video-player/video-player.component.html +++ b/apps/web/src/app/home/video-player/video-player.component.html @@ -1,6 +1,18 @@ - + - + @defer { } @loading { diff --git a/apps/web/src/app/home/video-player/video-player.component.scss b/apps/web/src/app/home/video-player/video-player.component.scss index c876706e4..4d7ad9be5 100644 --- a/apps/web/src/app/home/video-player/video-player.component.scss +++ b/apps/web/src/app/home/video-player/video-player.component.scss @@ -4,6 +4,7 @@ bottom: 0; left: 0; right: 0; + overflow: hidden; // Prevent horizontal scrollbar from resize handle } .mat-drawer-content { @@ -13,8 +14,18 @@ } .mat-drawer { - width: 400px; - min-width: 400px; + // Width is controlled by appResizable directive + // Default: 350px, min: 200px, max: 600px + overflow-x: hidden !important; // Hide horizontal scrollbar + + .resize-handle--right { + right: 0; + } +} + +// Ensure mat-drawer inner container fills the drawer width +::ng-deep .mat-drawer-inner-container { + width: 100% !important; } .custom-player { diff --git a/apps/web/src/app/home/video-player/video-player.component.ts b/apps/web/src/app/home/video-player/video-player.component.ts index da9cc285e..cc8f40a3c 100644 --- a/apps/web/src/app/home/video-player/video-player.component.ts +++ b/apps/web/src/app/home/video-player/video-player.component.ts @@ -23,6 +23,7 @@ import { HtmlVideoPlayerComponent, InfoOverlayComponent, MultiEpgContainerComponent, + ResizableDirective, SidebarComponent, ToolbarComponent, VjsPlayerComponent, @@ -64,6 +65,7 @@ import { SettingsComponent } from '../../settings/settings.component'; HtmlVideoPlayerComponent, InfoOverlayComponent, MatSidenavModule, + ResizableDirective, RouterLink, SidebarComponent, ToolbarComponent, diff --git a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.html b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.html index 53630e466..fab280587 100644 --- a/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.html +++ b/apps/web/src/app/shared/components/favorites-layout/favorites-layout.component.html @@ -1,4 +1,11 @@ -