diff --git a/lib/features/iptv/screens/player_screen.dart b/lib/features/iptv/screens/player_screen.dart index c0a3f6a..e6a116d 100644 --- a/lib/features/iptv/screens/player_screen.dart +++ b/lib/features/iptv/screens/player_screen.dart @@ -88,7 +88,8 @@ class _PlayerScreenState extends ConsumerState { final type = data['type']; if (type == 'playback_position' && _contentId.isNotEmpty) { final currentTime = (data['currentTime'] as num).toDouble(); - final duration = (data['duration'] as num).toDouble(); + final rawDuration = (data['duration'] as num).toDouble(); + final duration = rawDuration.isFinite ? rawDuration : 0.0; setState(() { _currentPosition = currentTime; _totalDuration = duration > 0 ? duration : 1; @@ -936,11 +937,11 @@ class _PlayerScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - _formatDuration(Duration(seconds: _currentPosition.toInt())), + _formatDuration(Duration(seconds: _currentPosition.isFinite ? _currentPosition.toInt() : 0)), style: const TextStyle(color: Colors.white, fontSize: 12), ), Text( - _formatDuration(Duration(seconds: _totalDuration.toInt())), + _formatDuration(Duration(seconds: _totalDuration.isFinite ? _totalDuration.toInt() : 0)), style: const TextStyle(color: Colors.white, fontSize: 12), ), ], diff --git a/lib/features/iptv/widgets/movies_tab.dart b/lib/features/iptv/widgets/movies_tab.dart index 3eefb66..3780d3e 100644 --- a/lib/features/iptv/widgets/movies_tab.dart +++ b/lib/features/iptv/widgets/movies_tab.dart @@ -140,6 +140,15 @@ class _MoviesTabState extends ConsumerState { return rating; } + /// Proxy HTTP images through backend to avoid CORS/mixed-content issues + String? _getProxiedImageUrl(String? originalUrl) { + if (originalUrl == null || originalUrl.isEmpty) return null; + if (originalUrl.startsWith('http://')) { + return '/api/xtream/$originalUrl'; + } + return originalUrl; + } + void _playMovie(Movie movie) { ref.read(watchHistoryProvider.notifier).markMovieWatched(movie.streamId); @@ -175,7 +184,7 @@ class _MoviesTabState extends ConsumerState { final heroItems = displayMovies.take(5).map((m) => HeroItem( id: m.streamId, title: m.name, - imageUrl: m.streamIcon ?? '', + imageUrl: _getProxiedImageUrl(m.streamIcon), subtitle: m.rating != null ? '${_formatRating(m.rating)} ★' : null, onMoreInfo: () { _playMovie(m); @@ -291,7 +300,7 @@ class _MoviesTabState extends ConsumerState { return MediaCard( title: movie.name, - imageUrl: movie.streamIcon, + imageUrl: _getProxiedImageUrl(movie.streamIcon), subtitle: movie.rating != null ? '${_formatRating(movie.rating)} ★' : null, rating: _formatRating(movie.rating), diff --git a/lib/features/iptv/widgets/series_tab.dart b/lib/features/iptv/widgets/series_tab.dart index e91beab..0487d65 100644 --- a/lib/features/iptv/widgets/series_tab.dart +++ b/lib/features/iptv/widgets/series_tab.dart @@ -140,6 +140,15 @@ class _SeriesTabState extends ConsumerState { return rating; } + /// Proxy HTTP images through backend to avoid CORS/mixed-content issues + String? _getProxiedImageUrl(String? originalUrl) { + if (originalUrl == null || originalUrl.isEmpty) return null; + if (originalUrl.startsWith('http://')) { + return '/api/xtream/$originalUrl'; + } + return originalUrl; + } + void _openSeries(Series series) { Navigator.push( context, @@ -173,7 +182,7 @@ class _SeriesTabState extends ConsumerState { final heroItems = displaySeries.take(5).map((s) => HeroItem( id: s.seriesId.toString(), // ID logic might differ for Series title: s.name, - imageUrl: s.cover ?? '', + imageUrl: _getProxiedImageUrl(s.cover), subtitle: s.rating != null ? '${_formatRating(s.rating)} ★' : null, onMoreInfo: () => _openSeries(s), )).toList(); @@ -281,7 +290,7 @@ class _SeriesTabState extends ConsumerState { final serie = displaySeries[index]; return MediaCard( title: serie.name, - imageUrl: serie.cover, + imageUrl: _getProxiedImageUrl(serie.cover), subtitle: serie.rating != null ? '${_formatRating(serie.rating)} ★' : null, rating: _formatRating(serie.rating), diff --git a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart index 5cab4d8..c88779b 100644 --- a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart @@ -125,6 +125,15 @@ class _MobileMoviesTabState extends ConsumerState { return rating; } + /// Proxy HTTP images through backend to avoid CORS/mixed-content issues + String? _getProxiedImageUrl(String? originalUrl) { + if (originalUrl == null || originalUrl.isEmpty) return null; + if (originalUrl.startsWith('http://')) { + return '/api/xtream/$originalUrl'; + } + return originalUrl; + } + void _playMovie(Movie movie) { ref.read(watchHistoryProvider.notifier).markMovieWatched(movie.streamId); @@ -160,7 +169,7 @@ class _MobileMoviesTabState extends ConsumerState { final heroItems = _movies.take(3).map((m) => HeroItem( id: m.streamId, title: m.name, - imageUrl: m.streamIcon ?? '', + imageUrl: _getProxiedImageUrl(m.streamIcon), subtitle: m.rating != null ? '${_formatRating(m.rating)} ★' : null, onMoreInfo: () => _playMovie(m), )).toList(); @@ -251,7 +260,7 @@ class _MobileMoviesTabState extends ConsumerState { return MediaCard( title: movie.name, - imageUrl: movie.streamIcon, + imageUrl: _getProxiedImageUrl(movie.streamIcon), subtitle: movie.rating != null ? '${_formatRating(movie.rating)} ★' : null, rating: _formatRating(movie.rating), diff --git a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart index 9c91206..a3e52d9 100644 --- a/lib/mobile/features/iptv/widgets/mobile_series_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_series_tab.dart @@ -124,6 +124,15 @@ class _MobileSeriesTabState extends ConsumerState { return rating; } + /// Proxy HTTP images through backend to avoid CORS/mixed-content issues + String? _getProxiedImageUrl(String? originalUrl) { + if (originalUrl == null || originalUrl.isEmpty) return null; + if (originalUrl.startsWith('http://')) { + return '/api/xtream/$originalUrl'; + } + return originalUrl; + } + void _openSeries(Series series) { Navigator.push( context, @@ -153,7 +162,7 @@ class _MobileSeriesTabState extends ConsumerState { final heroItems = _series.take(3).map((s) => HeroItem( id: s.seriesId.toString(), title: s.name, - imageUrl: s.cover ?? '', + imageUrl: _getProxiedImageUrl(s.cover), subtitle: s.rating != null ? '${_formatRating(s.rating)} ★' : null, onMoreInfo: () => _openSeries(s), )).toList(); @@ -251,7 +260,7 @@ class _MobileSeriesTabState extends ConsumerState { return MediaCard( title: series.name, - imageUrl: series.cover, + imageUrl: _getProxiedImageUrl(series.cover), subtitle: series.rating != null ? '${_formatRating(series.rating)} ★' : null, rating: _formatRating(series.rating), diff --git a/web/player.html b/web/player.html index fcbebf7..c106d2e 100644 --- a/web/player.html +++ b/web/player.html @@ -144,7 +144,7 @@ window.parent.postMessage({ type: 'playback_position', currentTime: video.currentTime, - duration: video.duration || 0 + duration: isFinite(video.duration) ? video.duration : 0 }, '*'); } } @@ -158,7 +158,7 @@ window.parent.postMessage({ type: 'playback_position', currentTime: video.currentTime, - duration: video.duration || 0 + duration: isFinite(video.duration) ? video.duration : 0 }, '*'); }); @@ -166,7 +166,7 @@ video.addEventListener('ended', () => { window.parent.postMessage({ type: 'playback_ended', - duration: video.duration || 0 + duration: isFinite(video.duration) ? video.duration : 0 }, '*'); });