diff --git a/apps/vod/api_views.py b/apps/vod/api_views.py
index 24eca04f..0f593558 100644
--- a/apps/vod/api_views.py
+++ b/apps/vod/api_views.py
@@ -298,8 +298,8 @@ class SeriesViewSet(viewsets.ReadOnlyModelViewSet):
'release_date': episode.release_date,
'plot': episode.description,
'duration': episode.duration,
- 'duration_secs': episode.duration * 60 if episode.duration else None,
'rating': episode.rating,
+ 'movie_image': episode.custom_properties.get('info', {}).get('movie_image') if episode.custom_properties else None,
'container_extension': episode.container_extension,
'type': 'episode',
'series': {
diff --git a/frontend/src/pages/VODs.jsx b/frontend/src/pages/VODs.jsx
index 288053ac..912116ff 100644
--- a/frontend/src/pages/VODs.jsx
+++ b/frontend/src/pages/VODs.jsx
@@ -222,6 +222,7 @@ const SeriesModal = ({ series, opened, onClose }) => {
const [detailedSeries, setDetailedSeries] = useState(null);
const [loadingDetails, setLoadingDetails] = useState(false);
const [activeTab, setActiveTab] = useState(null);
+ const [expandedEpisode, setExpandedEpisode] = useState(null);
useEffect(() => {
if (opened && series) {
@@ -324,6 +325,10 @@ const SeriesModal = ({ series, opened, onClose }) => {
return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
};
+ const handleEpisodeRowClick = (episode) => {
+ setExpandedEpisode(expandedEpisode === episode.id ? null : episode.id);
+ };
+
if (!series) return null;
// Use detailed data if available, otherwise use basic series data
@@ -539,52 +544,155 @@ const SeriesModal = ({ series, opened, onClose }) => {
{episodesBySeason[season]?.map(episode => (
- handlePlayEpisode(episode)}
- >
-
-
- {episode.episode_number || '?'}
-
-
-
-
-
- {episode.name}
-
- {episode.genre && (
-
- {episode.genre}
+
+ handleEpisodeRowClick(episode)}
+ >
+
+
+ {episode.episode_number || '?'}
+
+
+
+
+
+ {episode.name}
- )}
-
-
-
-
- {formatDuration(episode.duration)}
-
-
-
-
- {episode.release_date ? new Date(episode.release_date).toLocaleDateString() : 'N/A'}
-
-
-
- {
- e.stopPropagation();
- handlePlayEpisode(episode);
- }}
- >
-
-
-
-
+ {episode.genre && (
+
+ {episode.genre}
+
+ )}
+
+
+
+
+ {formatDuration(episode.duration)}
+
+
+
+
+ {episode.release_date ? new Date(episode.release_date).toLocaleDateString() : 'N/A'}
+
+
+
+ {
+ e.stopPropagation();
+ handlePlayEpisode(episode);
+ }}
+ >
+
+
+
+
+ {expandedEpisode === episode.id && (
+
+
+
+ {/* Episode Image and Description Row */}
+
+ {/* Episode Image */}
+ {episode.movie_image && (
+
+
+
+ )}
+
+ {/* Episode Description */}
+
+ {episode.description && (
+
+ Description
+
+ {episode.description}
+
+
+ )}
+
+
+
+ {/* Additional Episode Details */}
+
+ {episode.rating && (
+
+ Rating
+ {episode.rating}
+
+ )}
+
+ {episode.director && (
+
+ Director
+ {episode.director}
+
+ )}
+
+ {episode.actors && (
+
+ Cast
+ {episode.actors}
+
+ )}
+
+
+ {/* Technical Details */}
+ {(episode.bitrate || episode.video || episode.audio) && (
+
+ Technical Details
+
+ {episode.bitrate && episode.bitrate > 0 && (
+
+ Bitrate: {episode.bitrate} kbps
+
+ )}
+ {episode.video && Object.keys(episode.video).length > 0 && (
+
+ Video:{' '}
+ {episode.video.codec_long_name || episode.video.codec_name}
+ {episode.video.width && episode.video.height
+ ? `, ${episode.video.width}x${episode.video.height}`
+ : ''}
+
+ )}
+ {episode.audio && Object.keys(episode.audio).length > 0 && (
+
+ Audio:{' '}
+ {episode.audio.codec_long_name || episode.audio.codec_name}
+ {episode.audio.channels
+ ? `, ${episode.audio.channels} channels`
+ : ''}
+
+ )}
+
+
+ )}
+
+ {/* Provider Information */}
+ {episode.m3u_account && (
+
+ Provider:
+
+ {episode.m3u_account.name || episode.m3u_account}
+
+
+ )}
+
+
+
+ )}
+
))}
diff --git a/frontend/src/store/useVODStore.jsx b/frontend/src/store/useVODStore.jsx
index 630d31c6..ceb23ff7 100644
--- a/frontend/src/store/useVODStore.jsx
+++ b/frontend/src/store/useVODStore.jsx
@@ -307,7 +307,7 @@ const useVODStore = create((set, get) => ({
description: episode.plot || '',
season_number: parseInt(seasonNumber) || 0,
episode_number: episode.episode_number || 0,
- duration: episode.duration_secs ? Math.floor(episode.duration_secs / 60) : null,
+ duration: episode.duration ? Math.floor(episode.duration / 60) : null,
rating: episode.rating || '',
container_extension: episode.container_extension || '',
series: {
@@ -318,6 +318,7 @@ const useVODStore = create((set, get) => ({
uuid: episode.id, // Use the stream ID as UUID for playback
logo: episode.movie_image ? { url: episode.movie_image } : null,
release_date: episode.release_date || null,
+ movie_image: episode.movie_image || null,
};
episodesData[episode.id] = episodeData;
});