mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-22 22:58:14 +02:00
feat: Add generated Hive TypeAdapters for AppUser and PlaylistConfig models, and new build/doctor log files.
This commit is contained in:
1 parent
780f6c1842
commit
ef97c6dc90
81 files changed
+3233
-2428
No files matched your search
@@ -6,22 +6,21 @@ import 'package:video_player/video_player.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:xtremflow/core/models/iptv_models.dart';
|
||||
import 'package:xtremflow/core/models/playlist_config.dart';
|
||||
import 'package:xtremflow/features/iptv/services/xtream_service_mobile.dart';
|
||||
import 'package:xtremflow/mobile/providers/mobile_settings_providers.dart';
|
||||
import 'package:xtremflow/core/theme/app_colors.dart';
|
||||
import 'package:xtremflow/features/iptv/screens/native_player_screen.dart';
|
||||
import 'package:xtremflow/features/iptv/models/xtream_models.dart' as xm;
|
||||
import 'package:xtremflow/mobile/widgets/tv_focusable.dart';
|
||||
import 'package:xtremobile/core/models/iptv_models.dart' as model;
|
||||
import 'package:xtremobile/core/models/playlist_config.dart';
|
||||
import 'package:xtremobile/features/iptv/services/xtream_service_mobile.dart';
|
||||
import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
|
||||
import 'package:xtremobile/core/theme/app_colors.dart';
|
||||
import 'package:xtremobile/features/iptv/screens/native_player_screen.dart';
|
||||
import 'package:xtremobile/mobile/widgets/tv_focusable.dart';
|
||||
|
||||
class LitePlayerScreen extends ConsumerStatefulWidget {
|
||||
final String streamId;
|
||||
final String title;
|
||||
final StreamType streamType;
|
||||
final model.StreamType streamType;
|
||||
final String? containerExtension;
|
||||
final PlaylistConfig playlist;
|
||||
final List<Channel>? channels;
|
||||
final List<model.Channel>? channels;
|
||||
final int initialIndex;
|
||||
|
||||
// For series episodes
|
||||
@@ -59,7 +58,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
bool _isPlaying = false;
|
||||
|
||||
// EPG & Clock
|
||||
xm.ShortEPG? _epg;
|
||||
model.ShortEPG? _epg;
|
||||
Timer? _epgTimer;
|
||||
String _currentTime = "";
|
||||
Timer? _clockTimer;
|
||||
@@ -135,7 +134,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
await _xtreamService!.setPlaylistAsync(widget.playlist);
|
||||
_loadStream();
|
||||
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
_updateEPG();
|
||||
_epgTimer = Timer.periodic(
|
||||
const Duration(minutes: 5),
|
||||
@@ -148,7 +147,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
}
|
||||
|
||||
Future<void> _updateEPG() async {
|
||||
if (widget.streamType != StreamType.live || _xtreamService == null) return;
|
||||
if (widget.streamType != model.StreamType.live || _xtreamService == null) return;
|
||||
try {
|
||||
final currentChannelId =
|
||||
widget.channels?[_currentIndex].streamId ?? widget.streamId;
|
||||
@@ -179,11 +178,11 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
? widget.channels![_currentIndex].streamId
|
||||
: widget.streamId;
|
||||
|
||||
if (widget.streamType == StreamType.live) _updateEPG();
|
||||
if (widget.streamType == model.StreamType.live) _updateEPG();
|
||||
|
||||
final streamUrl = widget.streamType == StreamType.live
|
||||
final streamUrl = widget.streamType == model.StreamType.live
|
||||
? _xtreamService!.getLiveStreamUrl(currentStreamId)
|
||||
: (widget.streamType == StreamType.vod
|
||||
: (widget.streamType == model.StreamType.vod
|
||||
? _xtreamService!.getVodStreamUrl(
|
||||
currentStreamId,
|
||||
widget.containerExtension ?? 'mp4',
|
||||
@@ -210,7 +209,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
}
|
||||
|
||||
// Buffer Optimization: Pre-roll delay for TS streams
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
await Future.delayed(const Duration(milliseconds: 2000));
|
||||
}
|
||||
|
||||
@@ -489,7 +488,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Seeker (VOD)
|
||||
if (widget.streamType != StreamType.live) _buildSeeker(),
|
||||
if (widget.streamType != model.StreamType.live) _buildSeeker(),
|
||||
|
||||
// Unified Box: EPG & Controls
|
||||
Container(
|
||||
@@ -511,7 +510,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (widget.streamType == StreamType.live)
|
||||
if (widget.streamType == model.StreamType.live)
|
||||
TVFocusable(
|
||||
focusNode: _prevFocusNode,
|
||||
onPressed: () => _switchChannel(
|
||||
@@ -539,7 +538,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
if (widget.streamType == StreamType.live)
|
||||
if (widget.streamType == model.StreamType.live)
|
||||
TVFocusable(
|
||||
focusNode: _nextFocusNode,
|
||||
onPressed: () => _switchChannel(
|
||||
@@ -601,7 +600,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
}
|
||||
|
||||
Widget _buildEPGBox(String title) {
|
||||
String program = widget.streamType == StreamType.live
|
||||
String program = widget.streamType == model.StreamType.live
|
||||
? (_epg?.nowPlaying ?? "Pas d'infos EPG")
|
||||
: title;
|
||||
String next =
|
||||
@@ -646,7 +645,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (widget.streamType == StreamType.live &&
|
||||
if (widget.streamType == model.StreamType.live &&
|
||||
_epg?.nowPlaying != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
ClipRRect(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../core/models/playlist_config.dart';
|
||||
import '../../../widgets/mobile_scaffold.dart';
|
||||
import '../../../theme/mobile_theme.dart';
|
||||
import '../widgets/mobile_live_tv_tab.dart';
|
||||
import '../widgets/mobile_movies_tab.dart';
|
||||
import '../widgets/mobile_series_tab.dart';
|
||||
import '../widgets/mobile_settings_tab.dart';
|
||||
import '../../../providers/mobile_xtream_providers.dart';
|
||||
import 'package:xtremobile/core/models/playlist_config.dart';
|
||||
import 'package:xtremobile/mobile/widgets/mobile_scaffold.dart';
|
||||
import 'package:xtremobile/mobile/theme/mobile_theme.dart';
|
||||
import 'package:xtremobile/features/iptv/widgets/mobile_live_tv_tab.dart';
|
||||
import 'package:xtremobile/features/iptv/widgets/mobile_movies_tab.dart';
|
||||
import 'package:xtremobile/features/iptv/widgets/mobile_series_tab.dart';
|
||||
import 'package:xtremobile/features/iptv/widgets/mobile_settings_tab.dart';
|
||||
import 'package:xtremobile/mobile/providers/mobile_xtream_providers.dart';
|
||||
|
||||
class MobileDashboardScreen extends ConsumerStatefulWidget {
|
||||
final PlaylistConfig playlist;
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../../core/models/playlist_config.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../theme/mobile_theme.dart';
|
||||
import '../../../mobile/theme/mobile_theme.dart';
|
||||
import '../../../../core/services/playlist_api_service.dart';
|
||||
import 'mobile_dashboard_screen.dart';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import '../../../../core/services/playlist_api_service.dart';
|
||||
import '../../../../core/models/playlist_config.dart';
|
||||
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../theme/mobile_theme.dart';
|
||||
import '../../../mobile/theme/mobile_theme.dart';
|
||||
|
||||
final playlistsProvider = FutureProvider<List<PlaylistConfig>>((ref) async {
|
||||
final service = PlaylistApiService();
|
||||
|
||||
@@ -2,15 +2,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../../core/models/playlist_config.dart';
|
||||
import '../../../providers/mobile_xtream_providers.dart';
|
||||
import '../../../providers/mobile_settings_providers.dart';
|
||||
import 'native_player_screen.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/utils/image_cache_config.dart';
|
||||
import 'package:xtremobile/core/models/playlist_config.dart';
|
||||
import 'package:xtremobile/mobile/providers/mobile_xtream_providers.dart';
|
||||
import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
|
||||
import 'package:xtremobile/features/iptv/screens/native_player_screen.dart';
|
||||
import 'package:xtremobile/core/theme/app_colors.dart';
|
||||
import 'package:xtremobile/core/utils/image_cache_config.dart';
|
||||
import 'package:xtremobile/core/models/iptv_models.dart' as model;
|
||||
|
||||
class MobileSeriesDetailScreen extends ConsumerStatefulWidget {
|
||||
final Series series;
|
||||
final model.Series series;
|
||||
final PlaylistConfig playlist;
|
||||
|
||||
const MobileSeriesDetailScreen({
|
||||
@@ -26,7 +27,7 @@ class MobileSeriesDetailScreen extends ConsumerStatefulWidget {
|
||||
|
||||
class _MobileSeriesDetailScreenState
|
||||
extends ConsumerState<MobileSeriesDetailScreen> {
|
||||
SeriesInfo? _seriesInfo;
|
||||
model.SeriesInfo? _seriesInfo;
|
||||
bool _isLoading = true;
|
||||
String? _error;
|
||||
int _selectedSeason = 1;
|
||||
@@ -62,8 +63,9 @@ class _MobileSeriesDetailScreenState
|
||||
setState(() {
|
||||
_seriesInfo = info;
|
||||
_isLoading = false;
|
||||
if (info.episodes.isNotEmpty) {
|
||||
_selectedSeason = info.episodes.keys.first;
|
||||
if (_seriesInfo!.episodes.isNotEmpty) {
|
||||
final firstKey = _seriesInfo!.episodes.keys.first;
|
||||
_selectedSeason = int.tryParse(firstKey.toString()) ?? 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -137,10 +139,10 @@ class _MobileSeriesDetailScreenState
|
||||
background: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
if (_seriesInfo!.cover != null)
|
||||
if (_seriesInfo!.coverUrl != null)
|
||||
// [P1-2 FIX] Optimize series cover image cache (400x600 display)
|
||||
CachedNetworkImage(
|
||||
imageUrl: _seriesInfo!.cover!,
|
||||
imageUrl: _seriesInfo!.coverUrl!,
|
||||
fit: BoxFit.cover,
|
||||
memCacheWidth: 480,
|
||||
memCacheHeight: 720,
|
||||
@@ -175,7 +177,7 @@ class _MobileSeriesDetailScreenState
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
_seriesInfo!.name,
|
||||
_seriesInfo!.title,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -224,7 +226,7 @@ class _MobileSeriesDetailScreenState
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
_seriesInfo!.plot!,
|
||||
_seriesInfo?.plot ?? '',
|
||||
style: const TextStyle(
|
||||
color: AppColors.textSecondary,
|
||||
height: 1.4,
|
||||
@@ -256,7 +258,7 @@ class _MobileSeriesDetailScreenState
|
||||
selected: isSelected,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
setState(() => _selectedSeason = seasonNum);
|
||||
setState(() => _selectedSeason = int.tryParse(seasonNum.toString()) ?? 1);
|
||||
}
|
||||
},
|
||||
backgroundColor: isDark
|
||||
@@ -304,7 +306,7 @@ class _MobileSeriesDetailScreenState
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEpisodeTile(Episode episode) {
|
||||
Widget _buildEpisodeTile(model.Episode episode) {
|
||||
final watchHistory = ref.watch(mobileWatchHistoryProvider);
|
||||
final episodeKey = MobileWatchHistory.episodeKey(
|
||||
widget.series.seriesId,
|
||||
@@ -316,10 +318,9 @@ class _MobileSeriesDetailScreenState
|
||||
|
||||
// Calculate progress
|
||||
double progress = 0.0;
|
||||
if (episode.durationSecs != null &&
|
||||
episode.durationSecs! > 0 &&
|
||||
if (episode.duration > 0 &&
|
||||
resumePos > 0) {
|
||||
progress = (resumePos / episode.durationSecs!).clamp(0.0, 1.0);
|
||||
progress = (resumePos / episode.duration).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
// Force dark mode colors since background is hardcoded dark
|
||||
@@ -335,14 +336,14 @@ class _MobileSeriesDetailScreenState
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => NativePlayerScreen(
|
||||
streamId: episode.streamId,
|
||||
streamId: episode.id,
|
||||
title:
|
||||
"${widget.series.name} S${episode.seasonNum}E${episode.episodeNum}",
|
||||
"${widget.series.name} S${episode.season}E${episode.episodeNum}",
|
||||
playlist: widget.playlist,
|
||||
streamType: StreamType.series,
|
||||
containerExtension: episode.containerExtension ?? 'mp4',
|
||||
streamType: model.StreamType.series,
|
||||
containerExtension: episode.containerExtension,
|
||||
seriesId: widget.series.seriesId,
|
||||
season: episode.seasonNum,
|
||||
season: int.tryParse(episode.season.toString()),
|
||||
episodeNum: episode.episodeNum,
|
||||
// Pass explicit initial position for resume
|
||||
initialPosition:
|
||||
@@ -394,10 +395,9 @@ class _MobileSeriesDetailScreenState
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (episode.durationSecs != null &&
|
||||
episode.durationSecs! > 0)
|
||||
if (episode.duration > 0)
|
||||
Text(
|
||||
_formatDuration(episode.durationSecs!),
|
||||
_formatDuration(episode.duration),
|
||||
style: TextStyle(
|
||||
color: secondaryTextColor,
|
||||
fontSize: 12,
|
||||
|
||||
@@ -5,30 +5,29 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'package:xtremflow/mobile/widgets/tv_focusable.dart';
|
||||
import 'package:xtremobile/mobile/widgets/tv_focusable.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:xtremflow/core/models/iptv_models.dart';
|
||||
import 'package:xtremflow/core/models/playlist_config.dart';
|
||||
import 'package:xtremflow/features/iptv/services/xtream_service_mobile.dart';
|
||||
import 'package:xtremflow/mobile/providers/mobile_settings_providers.dart';
|
||||
import 'package:xtremflow/core/theme/app_colors.dart';
|
||||
import 'package:xtremflow/features/iptv/screens/lite_player_screen.dart';
|
||||
import 'package:xtremobile/core/models/iptv_models.dart' as model;
|
||||
import 'package:xtremobile/core/models/playlist_config.dart';
|
||||
import 'package:xtremobile/features/iptv/services/xtream_service_mobile.dart';
|
||||
import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
|
||||
import 'package:xtremobile/core/theme/app_colors.dart';
|
||||
import 'package:xtremobile/features/iptv/screens/lite_player_screen.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:xtremflow/mobile/providers/mobile_xtream_providers.dart';
|
||||
import 'package:xtremflow/core/utils/device_info.dart';
|
||||
import 'package:xtremobile/core/utils/device_info.dart';
|
||||
|
||||
/// Stream type enum for player
|
||||
enum StreamType { live, vod, series }
|
||||
/// Stream type enum for player
|
||||
// (Moved to global or imported from iptv_models if available)
|
||||
|
||||
/// Native video player screen for Android/iOS
|
||||
/// Uses media_kit (FFmpeg) for full codec support (AC3, EAC3, DTS, etc.)
|
||||
class NativePlayerScreen extends ConsumerStatefulWidget {
|
||||
final String streamId;
|
||||
final String title;
|
||||
final StreamType streamType;
|
||||
final model.StreamType streamType;
|
||||
final String? containerExtension;
|
||||
final PlaylistConfig playlist;
|
||||
final List<Channel>? channels;
|
||||
final List<model.Channel>? channels;
|
||||
final int initialIndex;
|
||||
|
||||
// For series episodes (to track watch history)
|
||||
@@ -160,7 +159,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
// Unified Buffering Logic (Live vs VOD)
|
||||
(_player.platform as dynamic)?.setProperty('cache', 'yes');
|
||||
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
// LIVE PROFILE: Increased stability, no longer low-latency (prevents cuts)
|
||||
debugPrint(
|
||||
'MediaKitPlayer: Applying LIVE optimization profile (STABILITY)',
|
||||
@@ -246,7 +245,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
|
||||
// Low-latency intentionally removed for stability
|
||||
/*
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
(_player.platform as dynamic)?.setProperty('profile', 'low-latency');
|
||||
}
|
||||
*/
|
||||
@@ -310,7 +309,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
_checkAndMarkWatched(position);
|
||||
|
||||
// Auto-save position every 10 seconds (resilience against app kill/crash)
|
||||
if (widget.streamType != StreamType.live && !_hasMarkedAsWatched) {
|
||||
if (widget.streamType != model.StreamType.live && !_hasMarkedAsWatched) {
|
||||
final now = DateTime.now();
|
||||
if (_lastSaveTime == null ||
|
||||
now.difference(_lastSaveTime!) > const Duration(seconds: 10)) {
|
||||
@@ -367,7 +366,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
_player.stream.completed.listen((completed) {
|
||||
if (completed && mounted) {
|
||||
// Logic for Live TV
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
debugPrint(
|
||||
'MediaKitPlayer: Live stream completed unexpectedly, attempting reconnect...',
|
||||
);
|
||||
@@ -431,7 +430,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
if (state == AppLifecycleState.paused ||
|
||||
state == AppLifecycleState.inactive ||
|
||||
state == AppLifecycleState.detached) {
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
// Force stop for Live TV
|
||||
_player.stop();
|
||||
} else {
|
||||
@@ -490,7 +489,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
|
||||
/// Get content ID for resume position storage
|
||||
String get _contentId {
|
||||
if (widget.streamType == StreamType.series &&
|
||||
if (widget.streamType == model.StreamType.series &&
|
||||
widget.seriesId != null &&
|
||||
widget.season != null &&
|
||||
widget.episodeNum != null) {
|
||||
@@ -505,7 +504,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
|
||||
/// Save resume position when exiting player
|
||||
void _saveResumePositionOnExit() {
|
||||
if (widget.streamType == StreamType.live) return;
|
||||
if (widget.streamType == model.StreamType.live) return;
|
||||
|
||||
debugPrint(
|
||||
'MediaKitPlayer: Saving position - pos: ${_position.inSeconds}s, dur: ${_duration.inSeconds}s, contentId: $_contentId',
|
||||
@@ -595,7 +594,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
Future.delayed(Duration(seconds: 2 * _reconnectAttempts), () {
|
||||
if (mounted) {
|
||||
// If VOD/Series, try to resume from current position
|
||||
if (widget.streamType != StreamType.live && _position.inSeconds > 0) {
|
||||
if (widget.streamType != model.StreamType.live && _position.inSeconds > 0) {
|
||||
_loadStream(startAt: _position);
|
||||
} else {
|
||||
_loadStream();
|
||||
@@ -608,7 +607,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
void _checkAndMarkWatched(Duration position) {
|
||||
// Skip if already marked, live TV, or no duration
|
||||
if (_hasMarkedAsWatched) return;
|
||||
if (widget.streamType == StreamType.live) return;
|
||||
if (widget.streamType == model.StreamType.live) return;
|
||||
if (_duration.inSeconds <= 0) return;
|
||||
|
||||
final progress = position.inSeconds / _duration.inSeconds;
|
||||
@@ -617,12 +616,12 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
_hasMarkedAsWatched = true;
|
||||
debugPrint('MediaKitPlayer: 80% reached, marking as watched');
|
||||
|
||||
if (widget.streamType == StreamType.vod) {
|
||||
if (widget.streamType == model.StreamType.vod) {
|
||||
// Mark movie as watched
|
||||
ref
|
||||
.read(mobileWatchHistoryProvider.notifier)
|
||||
.markMovieWatched(widget.streamId);
|
||||
} else if (widget.streamType == StreamType.series &&
|
||||
} else if (widget.streamType == model.StreamType.series &&
|
||||
widget.seriesId != null &&
|
||||
widget.season != null &&
|
||||
widget.episodeNum != null) {
|
||||
@@ -642,7 +641,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
/// Start watchdog timer for live streams
|
||||
void _startLiveWatchdog() {
|
||||
_liveWatchdog?.cancel();
|
||||
if (widget.streamType != StreamType.live) return;
|
||||
if (widget.streamType != model.StreamType.live) return;
|
||||
|
||||
// Check every 30 seconds if stream is still playing
|
||||
_liveWatchdog = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
@@ -678,7 +677,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
}
|
||||
}
|
||||
|
||||
ShortEPG? _epg;
|
||||
model.ShortEPG? _epg;
|
||||
|
||||
// ... (existing initState)
|
||||
|
||||
@@ -699,7 +698,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
: widget.streamId;
|
||||
|
||||
// Load EPG if Live TV
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
_loadEpg(currentStreamId);
|
||||
}
|
||||
|
||||
@@ -707,9 +706,9 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
// Build stream URL based on type
|
||||
String streamUrl;
|
||||
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
streamUrl = _xtreamService!.getLiveStreamUrl(currentStreamId);
|
||||
} else if (widget.streamType == StreamType.vod) {
|
||||
} else if (widget.streamType == model.StreamType.vod) {
|
||||
streamUrl = _xtreamService!.getVodStreamUrl(
|
||||
currentStreamId,
|
||||
widget.containerExtension ?? 'mp4',
|
||||
@@ -744,12 +743,12 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
});
|
||||
|
||||
// Show watchdog for live streams
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
_startLiveWatchdog();
|
||||
}
|
||||
|
||||
// Resume from saved position (VOD/Series only)
|
||||
if (widget.streamType != StreamType.live) {
|
||||
if (widget.streamType != model.StreamType.live) {
|
||||
// Priority 0: Explicit reconnect position (passed from _attemptReconnect)
|
||||
if (startAt != null && startAt.inSeconds > 0) {
|
||||
debugPrint(
|
||||
@@ -835,7 +834,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
if (_isPlaying) {
|
||||
_player.pause();
|
||||
// Save position on pause
|
||||
if (widget.streamType != StreamType.live) {
|
||||
if (widget.streamType != model.StreamType.live) {
|
||||
ref
|
||||
.read(mobileWatchHistoryProvider.notifier)
|
||||
.saveResumePosition(_contentId, _position.inSeconds);
|
||||
@@ -913,7 +912,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
// Left Arrow - Show controls or navigate, seek in VOD when hidden
|
||||
if (key == LogicalKeyboardKey.arrowLeft) {
|
||||
if (!_showControls) {
|
||||
if (widget.streamType != StreamType.live) {
|
||||
if (widget.streamType != model.StreamType.live) {
|
||||
final newPos = _position - const Duration(seconds: 10);
|
||||
_player.seek(newPos < Duration.zero ? Duration.zero : newPos);
|
||||
}
|
||||
@@ -926,7 +925,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
// Right Arrow - Show controls or navigate, seek in VOD when hidden
|
||||
if (key == LogicalKeyboardKey.arrowRight) {
|
||||
if (!_showControls) {
|
||||
if (widget.streamType != StreamType.live) {
|
||||
if (widget.streamType != model.StreamType.live) {
|
||||
final newPos = _position + const Duration(seconds: 10);
|
||||
if (newPos < _duration) {
|
||||
_player.seek(newPos);
|
||||
@@ -950,7 +949,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
|
||||
// Channel Down = Previous Channel (inverted as requested)
|
||||
if (key == LogicalKeyboardKey.channelDown) {
|
||||
if (widget.streamType == StreamType.live && widget.channels != null) {
|
||||
if (widget.streamType == model.StreamType.live && widget.channels != null) {
|
||||
_playPrevious();
|
||||
_onUserInteraction();
|
||||
return true;
|
||||
@@ -959,7 +958,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
|
||||
// Channel Up = Next Channel (inverted as requested)
|
||||
if (key == LogicalKeyboardKey.channelUp) {
|
||||
if (widget.streamType == StreamType.live && widget.channels != null) {
|
||||
if (widget.streamType == model.StreamType.live && widget.channels != null) {
|
||||
_playNext();
|
||||
_onUserInteraction();
|
||||
return true;
|
||||
@@ -1128,7 +1127,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (widget.streamType == StreamType.live)
|
||||
if (widget.streamType == model.StreamType.live)
|
||||
Text(
|
||||
_currentTime,
|
||||
style: const TextStyle(
|
||||
@@ -1387,7 +1386,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
||||
}
|
||||
|
||||
Widget _buildControlsOverlay(String title) {
|
||||
if (widget.streamType == StreamType.live) {
|
||||
if (widget.streamType == model.StreamType.live) {
|
||||
return _buildUnifiedOSD(title);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user