@@ -13,6 +13,20 @@ let activeVideoSrc = "";
1313let activeVideoContext = null ;
1414let activeSelectedSeason = null ; // Track the currently selected season
1515
16+ /**
17+ * Resolves a relative asset path against the app base path.
18+ * @param {string } path - Asset path.
19+ * @returns {string } Absolute asset URL.
20+ */
21+ function resolveAssetUrl ( path ) {
22+ if ( ! path ) return '' ;
23+ if ( / ^ h t t p s ? : \/ \/ / i. test ( path ) || path . startsWith ( 'data:' ) ) return path ;
24+ const base = ( typeof window !== 'undefined' && window . STREAMIT_BASE ) ? window . STREAMIT_BASE : '/' ;
25+ const safeBase = base . endsWith ( '/' ) ? base : `${ base } /` ;
26+ const cleaned = String ( path ) . replace ( / ^ \/ + / , '' ) ;
27+ return new URL ( cleaned , window . location . origin + safeBase ) . toString ( ) ;
28+ }
29+
1630/**
1731 * Opens media details and syncs route if available.
1832 * @param {Object } item - Media item.
@@ -50,7 +64,7 @@ export function setupHero(item) {
5064 const isSerie = item . type === 'serie' || item . seasons !== undefined ;
5165
5266 // Populate hero section elements with media data
53- document . getElementById ( 'heroImage' ) . src = item . banner || item . poster ;
67+ document . getElementById ( 'heroImage' ) . src = resolveAssetUrl ( item . banner || item . poster ) ;
5468 document . getElementById ( 'heroTitle' ) . innerText = item . title ;
5569 document . getElementById ( 'heroDesc' ) . innerText = item . description ;
5670 document . getElementById ( 'heroRating' ) . innerText = item . IMDb ;
@@ -83,13 +97,13 @@ export function setupHero(item) {
8397 episodeTitle : item . seasons [ "1" ] [ 0 ] . title || 'Épisode 1'
8498 } ;
8599 activeVideoContext = ctx ;
86- activeVideoSrc = item . seasons [ "1" ] [ 0 ] . video ;
100+ activeVideoSrc = resolveAssetUrl ( item . seasons [ "1" ] [ 0 ] . video ) ;
87101 playVideo ( activeVideoSrc , ctx ) ;
88102 } else if ( item . video ) {
89103 const ctx = { type : 'film' , title : item . title } ;
90104 activeVideoContext = ctx ;
91- activeVideoSrc = item . video ;
92- playVideo ( item . video , ctx ) ;
105+ activeVideoSrc = resolveAssetUrl ( item . video ) ;
106+ playVideo ( activeVideoSrc , ctx ) ;
93107 }
94108 } ;
95109
@@ -114,7 +128,7 @@ export function openDetails(item, preferredSeason = null) {
114128 const playBtn = document . getElementById ( 'detailPlayBtn' ) ;
115129
116130 // Populate detail overlay with media information
117- document . getElementById ( 'detailHeroImg' ) . src = item . banner || item . poster ;
131+ document . getElementById ( 'detailHeroImg' ) . src = resolveAssetUrl ( item . banner || item . poster ) ;
118132 document . getElementById ( 'detailTitle' ) . innerText = item . title ;
119133 document . getElementById ( 'detailDesc' ) . innerText = item . description ;
120134 document . getElementById ( 'detailYear' ) . innerText = item . year ;
@@ -191,7 +205,7 @@ export function openDetails(item, preferredSeason = null) {
191205 }
192206 } else {
193207 seriesSec . classList . add ( 'hidden' ) ;
194- activeVideoSrc = item . video ;
208+ activeVideoSrc = resolveAssetUrl ( item . video ) ;
195209 activeVideoContext = item . video ? { type : 'film' , title : item . title } : null ;
196210 }
197211
@@ -238,7 +252,7 @@ export function playCurrentMedia() {
238252 const firstSeasonKey = Object . keys ( activeDetailItem . seasons || { } ) [ 0 ] ;
239253 const firstEpisode = firstSeasonKey ? activeDetailItem . seasons [ firstSeasonKey ] ?. [ 0 ] : null ;
240254 if ( firstEpisode ) {
241- activeVideoSrc = firstEpisode . video ;
255+ activeVideoSrc = resolveAssetUrl ( firstEpisode . video ) ;
242256 activeVideoContext = {
243257 type : 'series' ,
244258 title : activeDetailItem . title ,
@@ -317,7 +331,7 @@ function renderEpisodes(episodes, seasonNum) {
317331 if ( isWatched ) row . classList . add ( 'episode-watched' ) ;
318332
319333 const fallbackThumb = `https://placehold.co/300x200/333/666?text=S${ safeSeasonNum } -EP${ idx + 1 } ` ;
320- const thumbUrl = activeDetailItem ? activeDetailItem . poster : fallbackThumb ;
334+ const thumbUrl = activeDetailItem ? resolveAssetUrl ( activeDetailItem . poster ) : fallbackThumb ;
321335
322336 // Create left container with episode number and thumbnail
323337 const leftContainer = document . createElement ( 'div' ) ;
@@ -406,9 +420,9 @@ function renderEpisodes(episodes, seasonNum) {
406420 episodeIndex : idx ,
407421 episodeTitle : ep . title || `Épisode ${ idx + 1 } ` ,
408422 } ;
409- activeVideoSrc = ep . video ;
423+ activeVideoSrc = resolveAssetUrl ( ep . video ) ;
410424 activeVideoContext = ctx ;
411- playVideo ( ep . video , ctx ) ;
425+ playVideo ( activeVideoSrc , ctx ) ;
412426 } ;
413427 list . appendChild ( row ) ;
414428 } ) ;
@@ -463,7 +477,7 @@ export function createMediaCard(item, extraClasses = "") {
463477 // Build media card HTML structure
464478 card . innerHTML = `
465479 ${ badgeStack }
466- <img src="${ item . poster } " alt="Poster for ${ item . title } " onerror="this.src='${ fallback } '" class="w-full h-full object-cover object-center transition-transform duration-700 loading='lazy'">
480+ <img src="${ resolveAssetUrl ( item . poster ) } " alt="Poster for ${ item . title } " onerror="this.src='${ fallback } '" class="w-full h-full object-cover object-center transition-transform duration-700 loading='lazy'">
467481 <div class="absolute inset-x-0 bottom-0 p-4 z-20 bg-gradient-to-t from-black/90 via-black/50 to-transparent translate-y-2 group-hover:translate-y-0 transition-transform duration-300">
468482 <h3 class="font-bold text-white text-base md:text-lg leading-tight mb-1 drop-shadow-md line-clamp-1">${ item . title } </h3>
469483 <div class="flex items-center gap-3 text-xs font-bold text-gray-300 opacity-0 group-hover:opacity-100 transition-opacity delay-100">
@@ -725,7 +739,7 @@ export function openActorDetails(actor, filmsData = {}, seriesData = {}) {
725739
726740 if ( ! overlay || ! actor ) return ;
727741
728- const photo = actor . photo || `https://placehold.co/500x700/111/fff?text=${ encodeURIComponent ( actor . name || 'Acteur' ) } ` ;
742+ const photo = actor . photo ? resolveAssetUrl ( actor . photo ) : `https://placehold.co/500x700/111/fff?text=${ encodeURIComponent ( actor . name || 'Acteur' ) } ` ;
729743 const filmography = Array . isArray ( actor . filmography ) ? [ ...actor . filmography ] : [ ] ;
730744 filmography . sort ( ( a , b ) => ( b . year || 0 ) - ( a . year || 0 ) ) ;
731745
@@ -805,7 +819,7 @@ export function renderActorsList(actorsData, filmsData = {}, seriesData = {}) {
805819
806820 // Create and append actor cards
807821 actors . forEach ( actor => {
808- const photo = actor . photo || `https://placehold.co/500x700/111/fff?text=${ encodeURIComponent ( actor . name || 'Acteur' ) } ` ;
822+ const photo = actor . photo ? resolveAssetUrl ( actor . photo ) : `https://placehold.co/500x700/111/fff?text=${ encodeURIComponent ( actor . name || 'Acteur' ) } ` ;
809823 const card = document . createElement ( 'div' ) ;
810824 card . className = "relative overflow-hidden rounded-2xl group shadow-lg bg-[#121212] border border-white/5 cursor-pointer actor-thumb" ;
811825 card . innerHTML = `
@@ -861,7 +875,7 @@ export function renderActorsListSearch(actorsData, filmsData = {}, seriesData =
861875 grid . className = "col-span-full grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6 gap-y-8" ;
862876
863877 actorsData . forEach ( actor => {
864- const photo = actor . photo || `https://placehold.co/500x700/111/fff?text=${ encodeURIComponent ( actor . name || 'Acteur' ) } ` ;
878+ const photo = actor . photo ? resolveAssetUrl ( actor . photo ) : `https://placehold.co/500x700/111/fff?text=${ encodeURIComponent ( actor . name || 'Acteur' ) } ` ;
865879 const card = document . createElement ( 'div' ) ;
866880 card . className = "relative overflow-hidden rounded-2xl group shadow-lg bg-[#121212] border border-white/5 cursor-pointer actor-thumb" ;
867881 card . innerHTML = `
0 commit comments