mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-19 05:09:42 +02:00
feat: XMLTV EPG fallback, Warm Cinema theme, background release
Align the Android app with xtremflow on three fronts. EPG: add XmltvService, a streaming XMLTV parser (gunzip-aware, indexed to a -3h/+18h window, JSON disk cache with a 6h TTL). XtreamServiceMobile now falls back through the provider's xmltv.php, a user-supplied URL, then a community guide whenever get_short_epg returns nothing. Channels are matched on epg_channel_id first, then on a normalized name (accents, HD/FHD/4K suffixes, "FR:" and "|FR|" prefixes). A streamId -> Channel index populated by getLiveChannels supplies those keys, so no getShortEPG call site changed. EPG requests are also funnelled through a 3-slot semaphore, as in xtremflow: a 30-tile grid used to fire 30 parallel player_api.php calls, which rate-limited panels answered with errors, emptying the guide for the whole screen. Theme: port the Warm Cinema charter (ember/charcoal/cream, Fraunces + Karla) into app_colors, app_theme and app_decorations. The 21 Apple TV colour names already in use are kept as compatibility aliases. mobile_theme now derives from AppTheme instead of duplicating its 390 lines. Playback lifecycle: stop reacting to AppLifecycleState.inactive, which fires on transient Android interruptions such as pulling down the notification shade and was killing playback. Both engines now release on paused/hidden/detached via _releasePlayback(): stream stopped, decoder released, timers cancelled, wakelock dropped. An _isReleased latch keeps the watchdog and reconnect paths from restarting a stream from the background. Long-run stability: cancel the six player stream subscriptions on dispose, cap demuxer-max-back-bytes at 8MB (mpv defaults to 50MiB, which combined with the 150MB readahead pushed the native heap toward an Android memory kill after a few hours), rebuild on position only while the controls are visible, and dispose the Lite player's focus nodes. Startup: prewarmHost() pre-resolves DNS and opens then closes a bare TCP/TLS socket before playback. No HTTP request is issued, so no provider connection slot is consumed. Capped at 1.5s. Verified: flutter analyze clean on all touched files; 19/19 tests, including a run over a real 46MB/69802-programme dump (517 channels indexed in 1.1s, 5/5 name probes matched); flutter build apk --release succeeds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
63e77c5bc5
commit
8d74bbb320
15 files changed
+2138
-852
No files matched your search
@@ -18,6 +18,12 @@ class MobileSettings {
|
||||
final List<String>
|
||||
deinterlacedChannels; // IDs of channels with forced deinterlacing
|
||||
|
||||
/// Custom XMLTV guide URL, tried after the provider's own `xmltv.php`.
|
||||
final String xmltvUrl;
|
||||
|
||||
/// Allow the built-in community XMLTV guides as the last EPG resort.
|
||||
final bool useCommunityEpg;
|
||||
|
||||
const MobileSettings({
|
||||
this.liveTvKeywords = const [],
|
||||
this.moviesKeywords = const [],
|
||||
@@ -30,6 +36,8 @@ class MobileSettings {
|
||||
this.deinterlace = false,
|
||||
this.bufferDuration = 0,
|
||||
this.deinterlacedChannels = const [],
|
||||
this.xmltvUrl = '',
|
||||
this.useCommunityEpg = true,
|
||||
});
|
||||
|
||||
MobileSettings copyWith({
|
||||
@@ -44,6 +52,8 @@ class MobileSettings {
|
||||
bool? deinterlace,
|
||||
int? bufferDuration,
|
||||
List<String>? deinterlacedChannels,
|
||||
String? xmltvUrl,
|
||||
bool? useCommunityEpg,
|
||||
}) {
|
||||
return MobileSettings(
|
||||
liveTvKeywords: liveTvKeywords ?? this.liveTvKeywords,
|
||||
@@ -57,6 +67,8 @@ class MobileSettings {
|
||||
deinterlace: deinterlace ?? this.deinterlace,
|
||||
bufferDuration: bufferDuration ?? this.bufferDuration,
|
||||
deinterlacedChannels: deinterlacedChannels ?? this.deinterlacedChannels,
|
||||
xmltvUrl: xmltvUrl ?? this.xmltvUrl,
|
||||
useCommunityEpg: useCommunityEpg ?? this.useCommunityEpg,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,6 +133,9 @@ class MobileSettingsNotifier extends StateNotifier<MobileSettings> {
|
||||
final buffer = _box?.get('bufferDuration', defaultValue: 0) as int?;
|
||||
final deinterlacedChannels =
|
||||
_box?.get('deinterlacedChannels', defaultValue: <String>[]) as List?;
|
||||
final xmltvUrl = _box?.get('xmltvUrl', defaultValue: '') as String?;
|
||||
final useCommunityEpg =
|
||||
_box?.get('useCommunityEpg', defaultValue: true) as bool?;
|
||||
|
||||
state = MobileSettings(
|
||||
liveTvKeywords: liveTv?.cast<String>() ?? [],
|
||||
@@ -134,6 +149,8 @@ class MobileSettingsNotifier extends StateNotifier<MobileSettings> {
|
||||
deinterlace: deinterlace ?? false,
|
||||
bufferDuration: buffer ?? 0,
|
||||
deinterlacedChannels: deinterlacedChannels?.cast<String>() ?? [],
|
||||
xmltvUrl: xmltvUrl ?? '',
|
||||
useCommunityEpg: useCommunityEpg ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,6 +204,17 @@ class MobileSettingsNotifier extends StateNotifier<MobileSettings> {
|
||||
_box?.put('bufferDuration', seconds);
|
||||
}
|
||||
|
||||
void setXmltvUrl(String url) {
|
||||
final trimmed = url.trim();
|
||||
state = state.copyWith(xmltvUrl: trimmed);
|
||||
_box?.put('xmltvUrl', trimmed);
|
||||
}
|
||||
|
||||
void toggleCommunityEpg(bool value) {
|
||||
state = state.copyWith(useCommunityEpg: value);
|
||||
_box?.put('useCommunityEpg', value);
|
||||
}
|
||||
|
||||
void toggleChannelDeinterlace(String streamId) {
|
||||
final currentList = List<String>.from(state.deinterlacedChannels);
|
||||
if (currentList.contains(streamId)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/models/playlist_config.dart';
|
||||
import '../../core/models/iptv_models.dart' as model;
|
||||
import '../../features/iptv/services/xtream_service_mobile.dart';
|
||||
import 'mobile_settings_providers.dart';
|
||||
|
||||
/// Mobile-specific Xtream service provider
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -13,6 +14,23 @@ final mobileXtreamServiceProvider =
|
||||
final service = XtreamServiceMobile(dir.path);
|
||||
await service
|
||||
.setPlaylistAsync(playlist); // Async to resolve DNS before API calls
|
||||
|
||||
// EPG fallback configuration follows the settings live, without tearing the
|
||||
// service (and its caches) down on every unrelated settings change.
|
||||
void applyEpgSettings(MobileSettings settings) => service.setXmltvConfig(
|
||||
customUrl: settings.xmltvUrl,
|
||||
allowPublicFallback: settings.useCommunityEpg,
|
||||
);
|
||||
|
||||
applyEpgSettings(ref.read(mobileSettingsProvider));
|
||||
ref.listen<MobileSettings>(mobileSettingsProvider, (previous, next) {
|
||||
if (previous?.xmltvUrl != next.xmltvUrl ||
|
||||
previous?.useCommunityEpg != next.useCommunityEpg) {
|
||||
applyEpgSettings(next);
|
||||
}
|
||||
});
|
||||
|
||||
ref.onDispose(service.dispose);
|
||||
return service;
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user