diff --git a/public/index.html b/public/index.html index dd74211..9205ef7 100644 --- a/public/index.html +++ b/public/index.html @@ -9,7 +9,7 @@ nodecast-tv | IPTV Player - + @@ -1095,20 +1095,20 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/public/js/api.js b/public/js/api.js index 77b1306..ed4b354 100644 --- a/public/js/api.js +++ b/public/js/api.js @@ -24,7 +24,7 @@ const API = { options.body = JSON.stringify(data); } - const response = await fetch(`/api${endpoint}`, options); + const response = await fetch(`api/${endpoint}`, options); let result; const contentType = response.headers.get('content-type'); @@ -39,7 +39,7 @@ const API = { // If unauthorized, redirect to login if (response.status === 401) { localStorage.removeItem('authToken'); - window.location.href = '/login.html'; + window.location.href = 'login.html'; return; } throw new Error(result.error || `Server responded with ${response.status}`); @@ -50,37 +50,37 @@ const API = { // Sources sources: { - getAll: () => API.request('GET', '/sources'), - getByType: (type) => API.request('GET', `/sources/type/${type}`), - getById: (id) => API.request('GET', `/sources/${id}`), - create: (data) => API.request('POST', '/sources', data), - update: (id, data) => API.request('PUT', `/sources/${id}`, data), - delete: (id) => API.request('DELETE', `/sources/${id}`), - toggle: (id) => API.request('POST', `/sources/${id}/toggle`), - test: (id) => API.request('POST', `/sources/${id}/test`), - sync: (id) => API.request('POST', `/sources/${id}/sync`), // Manual sync - getStatus: () => API.request('GET', '/sources/status'), // Get all statuses - estimate: (id) => API.request('GET', `/sources/${id}/estimate`), // Estimate M3U size - estimateByUrl: (url, type) => API.request('POST', '/sources/estimate', { url, type }), // Estimate by URL (before creation) + getAll: () => API.request('GET', 'sources'), + getByType: (type) => API.request('GET', `sources/type/${type}`), + getById: (id) => API.request('GET', `sources/${id}`), + create: (data) => API.request('POST', 'sources', data), + update: (id, data) => API.request('PUT', `sources/${id}`, data), + delete: (id) => API.request('DELETE', `sources/${id}`), + toggle: (id) => API.request('POST', `sources/${id}/toggle`), + test: (id) => API.request('POST', `sources/${id}/test`), + sync: (id) => API.request('POST', `sources/${id}/sync`), // Manual sync + getStatus: () => API.request('GET', 'sources/status'), // Get all statuses + estimate: (id) => API.request('GET', `sources/${id}/estimate`), // Estimate M3U size + estimateByUrl: (url, type) => API.request('POST', 'sources/estimate', { url, type }), // Estimate by URL (before creation) }, // Channels (hidden items) channels: { - getHidden: (sourceId = null) => API.request('GET', `/channels/hidden${sourceId ? `?sourceId=${sourceId}` : ''}`), - hide: (sourceId, itemType, itemId) => API.request('POST', '/channels/hide', { sourceId, itemType, itemId }), - show: (sourceId, itemType, itemId) => API.request('POST', '/channels/show', { sourceId, itemType, itemId }), - isHidden: (sourceId, itemType, itemId) => API.request('GET', `/channels/hidden/check?sourceId=${sourceId}&itemType=${itemType}&itemId=${itemId}`), - bulkHide: (items) => API.request('POST', '/channels/hide/bulk', { items }), - bulkShow: (items) => API.request('POST', '/channels/show/bulk', { items }), + getHidden: (sourceId = null) => API.request('GET', `channels/hidden${sourceId ? `?sourceId=${sourceId}` : ''}`), + hide: (sourceId, itemType, itemId) => API.request('POST', 'channels/hide', { sourceId, itemType, itemId }), + show: (sourceId, itemType, itemId) => API.request('POST', 'channels/show', { sourceId, itemType, itemId }), + isHidden: (sourceId, itemType, itemId) => API.request('GET', `channels/hidden/check?sourceId=${sourceId}&itemType=${itemType}&itemId=${itemId}`), + bulkHide: (items) => API.request('POST', 'channels/hide/bulk', { items }), + bulkShow: (items) => API.request('POST', 'channels/show/bulk', { items }), // Fast bulk operations - single SQL statement - showAll: (sourceId, contentType) => API.request('POST', '/channels/show/all', { sourceId, contentType }), - hideAll: (sourceId, contentType) => API.request('POST', '/channels/hide/all', { sourceId, contentType }) + showAll: (sourceId, contentType) => API.request('POST', 'channels/show/all', { sourceId, contentType }), + hideAll: (sourceId, contentType) => API.request('POST', 'channels/hide/all', { sourceId, contentType }) }, // Favorites favorites: { getAll: (sourceId = null, itemType = null) => { - let url = '/favorites'; + let url = 'favorites'; const params = []; if (sourceId) params.push(`sourceId=${sourceId}`); if (itemType) params.push(`itemType=${itemType}`); @@ -88,84 +88,84 @@ const API = { return API.request('GET', url); }, add: (sourceId, itemId, itemType = 'channel') => - API.request('POST', '/favorites', { sourceId, itemId, itemType }), + API.request('POST', 'favorites', { sourceId, itemId, itemType }), remove: (sourceId, itemId, itemType = 'channel') => - API.request('DELETE', '/favorites', { sourceId, itemId, itemType }), + API.request('DELETE', 'favorites', { sourceId, itemId, itemType }), check: (sourceId, itemId, itemType = 'channel') => - API.request('GET', `/favorites/check?sourceId=${sourceId}&itemId=${itemId}&itemType=${itemType}`) + API.request('GET', `favorites/check?sourceId=${sourceId}&itemId=${itemId}&itemType=${itemType}`) }, // Proxy proxy: { // Xtream xtream: { - auth: (sourceId) => API.request('GET', `/proxy/xtream/${sourceId}/auth`), + auth: (sourceId) => API.request('GET', `proxy/xtream/${sourceId}/auth`), liveCategories: (sourceId, options = {}) => { const params = options.includeHidden ? '?includeHidden=true' : ''; - return API.request('GET', `/proxy/xtream/${sourceId}/live_categories${params}`); + return API.request('GET', `proxy/xtream/${sourceId}/live_categories${params}`); }, liveStreams: (sourceId, categoryId = null, options = {}) => { const params = []; if (categoryId) params.push(`category_id=${categoryId}`); if (options.includeHidden) params.push('includeHidden=true'); const query = params.length ? `?${params.join('&')}` : ''; - return API.request('GET', `/proxy/xtream/${sourceId}/live_streams${query}`); + return API.request('GET', `proxy/xtream/${sourceId}/live_streams${query}`); }, vodCategories: (sourceId, options = {}) => { const params = options.includeHidden ? '?includeHidden=true' : ''; - return API.request('GET', `/proxy/xtream/${sourceId}/vod_categories${params}`); + return API.request('GET', `proxy/xtream/${sourceId}/vod_categories${params}`); }, vodStreams: (sourceId, categoryId = null, options = {}) => { const params = []; if (categoryId) params.push(`category_id=${categoryId}`); if (options.includeHidden) params.push('includeHidden=true'); const query = params.length ? `?${params.join('&')}` : ''; - return API.request('GET', `/proxy/xtream/${sourceId}/vod_streams${query}`); + return API.request('GET', `proxy/xtream/${sourceId}/vod_streams${query}`); }, seriesCategories: (sourceId, options = {}) => { const params = options.includeHidden ? '?includeHidden=true' : ''; - return API.request('GET', `/proxy/xtream/${sourceId}/series_categories${params}`); + return API.request('GET', `proxy/xtream/${sourceId}/series_categories${params}`); }, series: (sourceId, categoryId = null, options = {}) => { const params = []; if (categoryId) params.push(`category_id=${categoryId}`); if (options.includeHidden) params.push('includeHidden=true'); const query = params.length ? `?${params.join('&')}` : ''; - return API.request('GET', `/proxy/xtream/${sourceId}/series${query}`); + return API.request('GET', `proxy/xtream/${sourceId}/series${query}`); }, seriesInfo: (sourceId, seriesId) => - API.request('GET', `/proxy/xtream/${sourceId}/series_info?series_id=${seriesId}`), - shortEpg: (sourceId, streamId) => API.request('GET', `/proxy/xtream/${sourceId}/short_epg?stream_id=${streamId}`), + API.request('GET', `proxy/xtream/${sourceId}/series_info?series_id=${seriesId}`), + shortEpg: (sourceId, streamId) => API.request('GET', `proxy/xtream/${sourceId}/short_epg?stream_id=${streamId}`), getStreamUrl: (sourceId, streamId, type = 'live', container = 'm3u8') => - API.request('GET', `/proxy/xtream/${sourceId}/stream/${streamId}/${type}?container=${container}`) + API.request('GET', `proxy/xtream/${sourceId}/stream/${streamId}/${type}?container=${container}`) }, // EPG epg: { - get: (sourceId) => API.request('GET', `/proxy/epg/${sourceId}`), - getForChannels: (sourceId, channelIds) => API.request('POST', `/proxy/epg/${sourceId}/channels`, { channelIds }) + get: (sourceId) => API.request('GET', `proxy/epg/${sourceId}`), + getForChannels: (sourceId, channelIds) => API.request('POST', `proxy/epg/${sourceId}/channels`, { channelIds }) }, // Cache management cache: { - clear: (sourceId) => API.request('DELETE', `/proxy/cache/${sourceId}`) + clear: (sourceId) => API.request('DELETE', `proxy/cache/${sourceId}`) } }, // Settings settings: { - get: () => API.request('GET', '/settings'), - update: (data) => API.request('PUT', '/settings', data), - reset: () => API.request('DELETE', '/settings'), - getDefaults: () => API.request('GET', '/settings/defaults') + get: () => API.request('GET', 'settings'), + update: (data) => API.request('PUT', 'settings', data), + reset: () => API.request('DELETE', 'settings'), + getDefaults: () => API.request('GET', 'settings/defaults') }, // Users (admin only) users: { - getAll: () => API.request('GET', '/auth/users'), - create: (data) => API.request('POST', '/auth/users', data), - update: (id, data) => API.request('PUT', `/auth/users/${id}`, data), - delete: (id) => API.request('DELETE', `/auth/users/${id}`) + getAll: () => API.request('GET', 'auth/users'), + create: (data) => API.request('POST', 'auth/users', data), + update: (id, data) => API.request('PUT', `auth/users/${id}`, data), + delete: (id) => API.request('DELETE', `auth/users/${id}`) } }; diff --git a/public/js/app.js b/public/js/app.js index c4d2f8c..f00dc38 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -169,13 +169,13 @@ class App { if (!token) { // No token, redirect to login (replace to avoid back button issues) - window.location.replace('/login.html'); + window.location.replace('login.html'); return; } try { // Verify token with server - const response = await fetch('/api/auth/me', { + const response = await fetch('api/auth/me', { headers: { 'Authorization': `Bearer ${token}` } @@ -201,7 +201,7 @@ class App { } catch (err) { console.error('Authentication error:', err); localStorage.removeItem('authToken'); - window.location.replace('/login.html'); + window.location.replace('login.html'); } } @@ -225,7 +225,7 @@ class App { const token = localStorage.getItem('authToken'); if (token) { - await fetch('/api/auth/logout', { + await fetch('api/auth/logout', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` @@ -234,7 +234,7 @@ class App { } localStorage.removeItem('authToken'); - window.location.replace('/login.html'); + window.location.replace('login.html'); }); navbar.appendChild(logoutLink); @@ -283,7 +283,7 @@ document.addEventListener('DOMContentLoaded', () => { window.app = new App(); // Fetch and display version badge - fetch('/api/version') + fetch('api/version') .then(res => res.json()) .then(data => { const badge = document.getElementById('version-badge'); diff --git a/public/js/components/ChannelList.js b/public/js/components/ChannelList.js index e8da5ad..2047dbf 100644 --- a/public/js/components/ChannelList.js +++ b/public/js/components/ChannelList.js @@ -33,10 +33,10 @@ class ChannelList { * Only proxies HTTP URLs when on HTTPS page */ getProxiedImageUrl(url) { - if (!url || url.length === 0) return '/img/placeholder.png'; + if (!url || url.length === 0) return 'img/placeholder.png'; // Only proxy if we're on HTTPS and the image is HTTP if (window.location.protocol === 'https:' && url.startsWith('http://')) { - return `/api/proxy/image?url=${encodeURIComponent(url)}`; + return `api/proxy/image?url=${encodeURIComponent(url)}`; } return url; } @@ -510,7 +510,7 @@ class ChannelList { data-render-id="${renderId}" data-render-group="${renderGroup}"> + alt="" onerror="this.onerror=null;this.src='img/placeholder.png'">
${this.escapeHtml(channel.name)}
${this.escapeHtml(this.getProgramInfo(channel) || '')}
@@ -623,7 +623,7 @@ class ChannelList { data-render-id="${renderId}" data-render-group="${renderGroup}"> + alt="" onerror="this.onerror=null;this.src='img/placeholder.png'">
${this.escapeHtml(channel.name)}
${this.escapeHtml(this.getProgramInfo(channel) || '')}
@@ -1052,7 +1052,7 @@ class ChannelList { div.innerHTML = ` + alt="" onerror="this.onerror=null;this.src='img/placeholder.png'">
${this.escapeHtml(channel.name)}
${this.getProgramInfo(channel) || ''}
@@ -1305,7 +1305,7 @@ class ChannelList {
+ onerror="this.onerror=null;this.src='img/placeholder.png'" />

Group: ${this.escapeHtml(channel.groupTitle || 'Uncategorized')}

Source: ${channel.sourceType}

diff --git a/public/js/components/EpgGuide.js b/public/js/components/EpgGuide.js index ce4a01d..7b0da96 100644 --- a/public/js/components/EpgGuide.js +++ b/public/js/components/EpgGuide.js @@ -43,10 +43,10 @@ class EpgGuide { * Only proxies HTTP URLs when on HTTPS page */ getProxiedImageUrl(url) { - if (!url || url.length === 0) return '/img/placeholder.png'; + if (!url || url.length === 0) return 'img/placeholder.png'; // Only proxy if we're on HTTPS and the image is HTTP if (window.location.protocol === 'https:' && url.startsWith('http://')) { - return `/api/proxy/image?url=${encodeURIComponent(url)}`; + return `api/proxy/image?url=${encodeURIComponent(url)}`; } return url; } @@ -212,7 +212,7 @@ class EpgGuide { // Load EPG from ALL sources in parallel const fetchPromises = sources.map(async (source) => { try { - const response = await fetch(`/api/proxy/epg/${source.id}${queryParams}`); + const response = await fetch(`api/proxy/epg/${source.id}${queryParams}`); if (!response.ok) throw new Error(`Status ${response.status}`); return await response.json(); } catch (e) { @@ -596,7 +596,7 @@ class EpgGuide { ${isFavorite ? Icons.favorite : Icons.favoriteOutline} + alt="" onerror="this.onerror=null;this.src='img/placeholder.png'"> ${name}
diff --git a/public/js/components/VideoPlayer.js b/public/js/components/VideoPlayer.js index a089dab..ac3b71c 100644 --- a/public/js/components/VideoPlayer.js +++ b/public/js/components/VideoPlayer.js @@ -805,7 +805,7 @@ class VideoPlayer { async startTranscodeSession(url, options = {}) { try { console.log('[Player] Starting HLS transcode session...', options); - const res = await fetch('/api/transcode/session', { + const res = await fetch('api/transcode/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url, ...options }) @@ -817,7 +817,7 @@ class VideoPlayer { } catch (err) { console.error('[Player] Session start failed:', err); // Fallback to direct transcode if session fails - return `/api/transcode?url=${encodeURIComponent(url)}`; + return `api/transcode?url=${encodeURIComponent(url)}`; } } @@ -829,7 +829,7 @@ class VideoPlayer { console.log('[Player] Stopping transcode session:', this.currentSessionId); try { // Fire and forget cleanup - fetch(`/api/transcode/${this.currentSessionId}`, { method: 'DELETE' }); + fetch(`api/transcode/${this.currentSessionId}`, { method: 'DELETE' }); } catch (err) { console.error('Failed to stop session:', err); } @@ -865,7 +865,7 @@ class VideoPlayer { if (this.settings.autoTranscode) { console.log('[Player] Auto Transcode enabled. Probing stream...'); try { - const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(streamUrl)}`); + const probeRes = await fetch(`api/probe?url=${encodeURIComponent(streamUrl)}`); const info = await probeRes.json(); console.log(`[Player] Probe result: video=${info.video}, audio=${info.audio}, ${info.width}x${info.height}, compatible=${info.compatible}`); @@ -885,7 +885,7 @@ class VideoPlayer { track.kind = 'subtitles'; track.label = sub.title; track.srclang = sub.language; - track.src = `/api/subtitle?url=${encodeURIComponent(streamUrl)}&index=${sub.index}`; + track.src = `api/subtitle?url=${encodeURIComponent(streamUrl)}&index=${sub.index}`; this.video.appendChild(track); }); @@ -925,7 +925,7 @@ class VideoPlayer { // Raw .ts container - use remux console.log('[Player] Auto: Using remux (.ts container)'); this.updateTranscodeStatus('remuxing', 'Remux (Auto)'); - const remuxUrl = `/api/remux?url=${encodeURIComponent(streamUrl)}`; + const remuxUrl = `api/remux?url=${encodeURIComponent(streamUrl)}`; this.currentUrl = remuxUrl; this.video.src = remuxUrl; this.video.play().catch(e => { @@ -997,7 +997,7 @@ class VideoPlayer { // Probe to get video codec for HEVC tag handling let videoCodec = 'unknown'; try { - const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(streamUrl)}`); + const probeRes = await fetch(`api/probe?url=${encodeURIComponent(streamUrl)}`); const info = await probeRes.json(); videoCodec = info.video; } catch (e) { console.warn('Probe failed for force audio, assuming h264'); } @@ -1096,7 +1096,7 @@ class VideoPlayer { (data.type === Hls.ErrorTypes.MEDIA_ERROR && data.details === 'fragParsingError'); // Don't proxy if it's already a local API URL - const isLocalApi = this.currentUrl.startsWith('/api/'); + const isLocalApi = this.currentUrl.startsWith('api/'); if (isCorsLikely && !this.isUsingProxy && !isLocalApi) { console.log('CORS/Network error detected, retrying via proxy...', data.details); @@ -1344,14 +1344,14 @@ class VideoPlayer { * Get proxied URL for a stream */ getProxiedUrl(url) { - return `/api/proxy/stream?url=${encodeURIComponent(url)}`; + return `api/proxy/stream?url=${encodeURIComponent(url)}`; } /** * Get transcoded URL for a stream (audio transcoding for browser compatibility) */ getTranscodeUrl(url) { - return `/api/transcode?url=${encodeURIComponent(url)}`; + return `api/transcode?url=${encodeURIComponent(url)}`; } /** @@ -1359,7 +1359,7 @@ class VideoPlayer { * Used for raw .ts streams that browsers can't play directly */ getRemuxUrl(url) { - return `/api/remux?url=${encodeURIComponent(url)}`; + return `api/remux?url=${encodeURIComponent(url)}`; } /** diff --git a/public/js/login.js b/public/js/login.js index a246d10..e2339f3 100644 --- a/public/js/login.js +++ b/public/js/login.js @@ -5,10 +5,10 @@ document.getElementById('login-form').addEventListener('submit', async (e) => { const password = document.getElementById('password').value; try { - const response = await API.request('POST', '/auth/login', { username, password }); + const response = await API.request('POST', 'auth/login', { username, password }); localStorage.setItem('sessionToken', response.token); - window.location.href = '/'; + window.location.href = 'index.html'; } catch (err) { document.getElementById('login-error').textContent = 'Login failed: ' + err.message; } -}); \ No newline at end of file +}); diff --git a/public/js/pages/HomePage.js b/public/js/pages/HomePage.js index 6ddadc1..c8a1805 100644 --- a/public/js/pages/HomePage.js +++ b/public/js/pages/HomePage.js @@ -174,7 +174,7 @@ class HomePage { await this.renderFavoriteChannels(); // 1. Load Watch History - const history = await window.API.request('GET', '/history?limit=12'); + const history = await window.API.request('GET', 'history?limit=12'); if (history && Array.isArray(history)) { this.renderHistory(history); } @@ -197,7 +197,7 @@ class HomePage { try { // Fetch favorite channels for current user - const favorites = await window.API.request('GET', '/favorites?itemType=channel'); + const favorites = await window.API.request('GET', 'favorites?itemType=channel'); if (!favorites || favorites.length === 0) { list.innerHTML = '
Add channels to favorites from Live TV
'; @@ -251,14 +251,14 @@ class HomePage { } createChannelTile(channel) { - const logo = channel.tvgLogo || '/img/placeholder.png'; - const logoUrl = logo.startsWith('http') ? `/api/proxy/image?url=${encodeURIComponent(logo)}` : logo; + const logo = channel.tvgLogo || 'img/placeholder.png'; + const logoUrl = logo.startsWith('http') ? `api/proxy/image?url=${encodeURIComponent(logo)}` : logo; const name = channel.name || 'Unknown'; return `
${name}
@@ -352,7 +352,7 @@ class HomePage { if (!list) return; try { - const movies = await window.API.request('GET', '/channels/recent?type=movie&limit=12'); + const movies = await window.API.request('GET', 'channels/recent?type=movie&limit=12'); if (!movies || movies.length === 0) { list.innerHTML = '
No recently added movies found
'; return; @@ -381,7 +381,7 @@ class HomePage { if (!list) return; try { - const series = await window.API.request('GET', '/channels/recent?type=series&limit=12'); + const series = await window.API.request('GET', 'channels/recent?type=series&limit=12'); if (!series || series.length === 0) { list.innerHTML = '
No recently added series found
'; return; @@ -411,13 +411,13 @@ class HomePage { const percent = Math.min(100, Math.round((progress / duration) * 100)); // Proxy the poster if it's an external URL - const poster = data.poster || '/img/poster-placeholder.jpg'; - const posterUrl = poster.startsWith('http') ? `/api/proxy/image?url=${encodeURIComponent(poster)}` : poster; + const poster = data.poster || 'img/poster-placeholder.jpg'; + const posterUrl = poster.startsWith('http') ? `api/proxy/image?url=${encodeURIComponent(poster)}` : poster; return `
- ${data.title || item.name} + ${data.title || item.name}
@@ -436,13 +436,13 @@ class HomePage { createRecentCard(item) { const { data, item_id } = item; const type = item.type || item.item_type; - const poster = item.stream_icon || data.poster || '/img/poster-placeholder.jpg'; - const posterUrl = poster.startsWith('http') ? `/api/proxy/image?url=${encodeURIComponent(poster)}` : poster; + const poster = item.stream_icon || data.poster || 'img/poster-placeholder.jpg'; + const posterUrl = poster.startsWith('http') ? `api/proxy/image?url=${encodeURIComponent(poster)}` : poster; return `
- ${item.name} + ${item.name}
@@ -465,7 +465,7 @@ class HomePage { const streamId = item.item_id; const container = item.container_extension || (item.data && item.data.containerExtension) || 'mp4'; - const result = await window.API.request('GET', `/proxy/xtream/${sourceId}/stream/${streamId}/${streamType}?container=${container}`); + const result = await window.API.request('GET', `proxy/xtream/${sourceId}/stream/${streamId}/${streamType}?container=${container}`); if (result && result.url) { const content = { @@ -488,7 +488,7 @@ class HomePage { // Fetch seriesInfo if we have a seriesId if (content.seriesId && sourceId) { try { - const seriesInfo = await window.API.request('GET', `/proxy/xtream/${sourceId}/series_info?series_id=${content.seriesId}`); + const seriesInfo = await window.API.request('GET', `proxy/xtream/${sourceId}/series_info?series_id=${content.seriesId}`); if (seriesInfo) { content.seriesInfo = seriesInfo; } diff --git a/public/js/pages/MoviesPage.js b/public/js/pages/MoviesPage.js index a5bb40f..c27fba6 100644 --- a/public/js/pages/MoviesPage.js +++ b/public/js/pages/MoviesPage.js @@ -278,7 +278,7 @@ class MoviesPage { card.dataset.movieId = movie.stream_id; card.dataset.sourceId = movie.sourceId; - const poster = movie.stream_icon || movie.cover || '/img/placeholder.png'; + const poster = movie.stream_icon || movie.cover || 'img/placeholder.png'; const year = movie.year || movie.releaseDate?.substring(0, 4) || ''; const rating = movie.rating ? `${Icons.star} ${movie.rating}` : ''; @@ -287,7 +287,7 @@ class MoviesPage { card.innerHTML = `
${movie.name} + onerror="this.onerror=null;this.src='img/placeholder.png'" loading="lazy">
${Icons.play}
diff --git a/public/js/pages/SeriesPage.js b/public/js/pages/SeriesPage.js index 6b7a929..1a25f85 100644 --- a/public/js/pages/SeriesPage.js +++ b/public/js/pages/SeriesPage.js @@ -288,7 +288,7 @@ class SeriesPage { card.dataset.seriesId = series.series_id; card.dataset.sourceId = series.sourceId; - const poster = series.cover || '/img/placeholder.png'; + const poster = series.cover || 'img/placeholder.png'; const year = series.year || series.releaseDate?.substring(0, 4) || ''; const rating = series.rating ? `${Icons.star} ${series.rating}` : ''; @@ -297,7 +297,7 @@ class SeriesPage { card.innerHTML = `
${series.name} + onerror="this.onerror=null;this.src='img/placeholder.png'" loading="lazy">
${Icons.play}
@@ -350,7 +350,7 @@ class SeriesPage { this.detailsPanel.classList.remove('hidden'); // Set header info - document.getElementById('series-poster').src = series.cover || '/img/placeholder.png'; + document.getElementById('series-poster').src = series.cover || 'img/placeholder.png'; document.getElementById('series-title').textContent = series.name; document.getElementById('series-plot').textContent = series.plot || ''; diff --git a/public/js/pages/Settings.js b/public/js/pages/Settings.js index a47b5d7..437d7a5 100644 --- a/public/js/pages/Settings.js +++ b/public/js/pages/Settings.js @@ -259,7 +259,7 @@ class SettingsPage { if (!container) return; try { - const response = await fetch('/api/settings/hw-info'); + const response = await fetch('api/settings/hw-info'); if (!response.ok) throw new Error('Failed to fetch hardware info'); const hwInfo = await response.json(); @@ -603,7 +603,7 @@ class SettingsPage { try { // Fetch last sync time from server - const response = await fetch('/api/settings/sync-status'); + const response = await fetch('api/settings/sync-status'); if (!response.ok) throw new Error('Failed to fetch sync status'); const data = await response.json(); diff --git a/public/js/pages/WatchPage.js b/public/js/pages/WatchPage.js index b85ca63..2aecdfd 100644 --- a/public/js/pages/WatchPage.js +++ b/public/js/pages/WatchPage.js @@ -329,7 +329,7 @@ class WatchPage { async startTranscodeSession(url, options = {}) { try { console.log('[WatchPage] Starting HLS transcode session...', options); - const res = await fetch('/api/transcode/session', { + const res = await fetch('api/transcode/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -345,7 +345,7 @@ class WatchPage { } catch (err) { console.error('[WatchPage] Session start failed:', err); // Fallback to direct transcode if session fails - return `/api/transcode?url=${encodeURIComponent(url)}`; + return `api/transcode?url=${encodeURIComponent(url)}`; } } @@ -357,7 +357,7 @@ class WatchPage { console.log('[WatchPage] Stopping transcode session:', this.currentSessionId); try { // Fire and forget cleanup - fetch(`/api/transcode/${this.currentSessionId}`, { method: 'DELETE' }); + fetch(`api/transcode/${this.currentSessionId}`, { method: 'DELETE' }); } catch (err) { console.error('Failed to stop session:', err); } @@ -437,7 +437,7 @@ class WatchPage { console.log('[WatchPage] Auto Transcode enabled. Probing stream...'); try { const ua = settings.userAgentPreset === 'custom' ? settings.userAgentCustom : settings.userAgentPreset; - const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}`); + const probeRes = await fetch(`api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}`); const info = await probeRes.json(); console.log(`[WatchPage] Probe result: video=${info.video}, audio=${info.audio}, ${info.width}x${info.height}, compatible=${info.compatible}`); @@ -470,7 +470,7 @@ class WatchPage { // TODO: Move remux to session logic if seeking is needed for TS files console.log('[WatchPage] Auto: Using remux (.ts container)'); this.updateTranscodeStatus('remuxing', 'Remux (Auto)'); - const finalUrl = `/api/remux?url=${encodeURIComponent(url)}`; + const finalUrl = `api/remux?url=${encodeURIComponent(url)}`; this.video.src = finalUrl; this.video.play().catch(e => { if (e.name !== 'AbortError') console.error('[WatchPage] Autoplay error:', e); @@ -509,7 +509,7 @@ class WatchPage { let videoCodec = 'unknown'; try { const ua = settings.userAgentPreset === 'custom' ? settings.userAgentCustom : settings.userAgentPreset; - const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}`); + const probeRes = await fetch(`api/probe?url=${encodeURIComponent(url)}&ua=${encodeURIComponent(ua || '')}`); const info = await probeRes.json(); videoCodec = info.video; } catch (e) { console.warn('Probe failed for force audio, assuming h264'); } @@ -528,7 +528,7 @@ class WatchPage { if (settings.forceRemux && isRawTs) { console.log('[WatchPage] Force Remux enabled'); this.updateTranscodeStatus('remuxing', 'Remux (Force)'); - const finalUrl = `/api/remux?url=${encodeURIComponent(url)}`; + const finalUrl = `api/remux?url=${encodeURIComponent(url)}`; this.video.src = finalUrl; this.video.play().catch(e => { if (e.name !== 'AbortError') console.error('[WatchPage] Autoplay error:', e); @@ -540,7 +540,7 @@ class WatchPage { // Determine if proxy is needed const proxyRequiredDomains = ['pluto.tv']; const needsProxy = settings.forceProxy || proxyRequiredDomains.some(domain => url.includes(domain)); - const finalUrl = needsProxy ? `/api/proxy/stream?url=${encodeURIComponent(url)}` : url; + const finalUrl = needsProxy ? `api/proxy/stream?url=${encodeURIComponent(url)}` : url; console.log('[WatchPage] Playing:', { url, needsProxy, looksLikeHls }); @@ -600,9 +600,9 @@ class WatchPage { console.error('[WatchPage] HLS fatal error:', data); // Try proxy on CORS error (only if not already proxied/transcoded) // Note: Transcoded streams are local, so no CORS issues usually - if (!url.startsWith('/api/') && (data.type === Hls.ErrorTypes.NETWORK_ERROR)) { + if (!url.startsWith('api/') && (data.type === Hls.ErrorTypes.NETWORK_ERROR)) { console.log('[WatchPage] Retrying via proxy...'); - this.playHls(`/api/proxy/stream?url=${encodeURIComponent(this.currentUrl)}`); + this.playHls(`api/proxy/stream?url=${encodeURIComponent(this.currentUrl)}`); } else { this.hls.destroy(); } @@ -1030,7 +1030,7 @@ class WatchPage { if (!this.content) return; const isChannel = this.content.type === 'channel' || !this.content.type; // Default to channel if unknown - const fallback = isChannel ? '/img/placeholder.png' : '/img/poster-placeholder.jpg'; + const fallback = isChannel ? 'img/placeholder.png' : 'img/poster-placeholder.jpg'; this.posterEl.onerror = () => { this.posterEl.onerror = null; @@ -1130,9 +1130,9 @@ class WatchPage { this.recommendedGrid.innerHTML = movies.map(movie => ` `).join(''); @@ -1417,7 +1417,7 @@ class WatchPage { currentEpisode: this.currentEpisode || null }; - await window.API.request('POST', '/history', { + await window.API.request('POST', 'history', { id: this.content.id, type: this.content.type === 'movie' ? 'movie' : 'episode', sourceId: this.content.sourceId, diff --git a/public/login.html b/public/login.html index 454acfe..34bcb46 100644 --- a/public/login.html +++ b/public/login.html @@ -5,7 +5,7 @@ Login - NodeCast TV - +