mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-12 01:36:20 +02:00
fix(streaming): optimize quality, audio sync, and content update refresh
- Increased FFmpeg video quality to 8Mbps and audio to 192kbps. - Added audio resync filters and better presets. - Optimized HLS buffer settings for mobile players to reduce interruptions. - Reduced API cache duration to 15m and added refresh support for faster content updates.
This commit is contained in:
1 parent
1377b70aee
commit
33d0e0a80d
4 files changed
+57
-45
No files matched your search
@@ -153,16 +153,17 @@ Handler createLiveStreamHandler(
|
||||
'-i', targetUrl,
|
||||
// Video
|
||||
if (useNvidiaGpu) ...[
|
||||
'-c:v', 'h264_nvenc', '-preset', 'p1', '-tune', 'll',
|
||||
'-b:v', '3500k', '-maxrate', '4000k', '-bufsize', '7000k',
|
||||
'-g', '50', // Force keyframes every 2s
|
||||
'-c:v', 'h264_nvenc', '-preset', 'p4', '-tune', 'hq', // Better quality preset for NVENC
|
||||
'-b:v', '8000k', '-maxrate', '12000k', '-bufsize', '16000k', // Quality native / high bitrate
|
||||
'-g', '50',
|
||||
] else ...[
|
||||
'-c:v', 'libx264', '-preset', 'ultrafast', '-tune', 'zerolatency',
|
||||
'-b:v', '3000k', '-maxrate', '3500k', '-bufsize', '6000k',
|
||||
'-g', '50', // Force keyframes every 2s (CRITICAL for HLS)
|
||||
'-c:v', 'libx264', '-preset', 'medium', '-tune', 'zerolatency', // Better detail than ultrafast
|
||||
'-b:v', '6000k', '-maxrate', '8000k', '-bufsize', '12000k',
|
||||
'-g', '50',
|
||||
],
|
||||
// Audio
|
||||
'-c:a', 'aac', '-b:a', '128k', '-ac', '2',
|
||||
// Audio: High quality + Sync filter
|
||||
'-c:a', 'aac', '-b:a', '192k', '-ac', '2', '-ar', '48000',
|
||||
'-af', 'aresample=async=1',
|
||||
// HLS Sliding Window
|
||||
'-f', 'hls',
|
||||
'-hls_time', '2',
|
||||
@@ -390,20 +391,16 @@ Handler createVodStreamHandler(
|
||||
'-c:v',
|
||||
'libx264',
|
||||
'-preset',
|
||||
'ultrafast',
|
||||
'-tune',
|
||||
'zerolatency',
|
||||
'-tune',
|
||||
'fastdecode',
|
||||
'medium', // Quality over raw speed for VOD
|
||||
'-crf',
|
||||
'23',
|
||||
'18', // Near-native visual quality
|
||||
'-maxrate',
|
||||
'3000k',
|
||||
'12000k',
|
||||
'-bufsize',
|
||||
'6000k',
|
||||
'24000k',
|
||||
'-pix_fmt',
|
||||
'yuv420p',
|
||||
'-g', '48', // Force keyframes
|
||||
'-g', '48',
|
||||
'-threads',
|
||||
'0',
|
||||
]);
|
||||
|
||||
@@ -28,8 +28,8 @@ class XtreamService {
|
||||
// Use MemCacheStore on Web to avoid path errors
|
||||
_cacheOptions = CacheOptions(
|
||||
store: kIsWeb ? MemCacheStore() : HiveCacheStore('./cache'),
|
||||
policy: CachePolicy.forceCache,
|
||||
maxStale: const Duration(hours: 1),
|
||||
policy: CachePolicy.refresh, // Allow refresh if stale
|
||||
maxStale: const Duration(minutes: 15), // Reduced from 1h to 15m for better sync
|
||||
priority: CachePriority.high,
|
||||
);
|
||||
|
||||
@@ -112,7 +112,7 @@ class XtreamService {
|
||||
'username': _currentPlaylist!.username,
|
||||
'password': _currentPlaylist!.password,
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
return response.data as Map<String, dynamic>;
|
||||
@@ -133,7 +133,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_live_categories',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> categories = response.data as List<dynamic>;
|
||||
@@ -157,9 +157,13 @@ class XtreamService {
|
||||
/// Get all live TV channels grouped by category
|
||||
///
|
||||
/// Returns a Map where key is category name and value is list of channels
|
||||
Future<Map<String, List<Channel>>> getLiveChannels() async {
|
||||
Future<Map<String, List<Channel>>> getLiveChannels({bool refresh = false}) async {
|
||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||
|
||||
final options = refresh
|
||||
? _cacheOptions.copyWith(policy: CachePolicy.refreshForceCache).toExtra()
|
||||
: _cacheOptions.toExtra();
|
||||
|
||||
try {
|
||||
// First, load categories to get the mapping
|
||||
final categoryMap = await _getLiveCategories();
|
||||
@@ -171,7 +175,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_live_streams',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> streams = response.data as List<dynamic>;
|
||||
@@ -212,7 +216,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_vod_categories',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> categories = response.data as List<dynamic>;
|
||||
@@ -232,9 +236,13 @@ class XtreamService {
|
||||
}
|
||||
|
||||
/// Get all VOD items (movies) grouped by category
|
||||
Future<Map<String, List<VodItem>>> getVodItems() async {
|
||||
Future<Map<String, List<VodItem>>> getVodItems({bool refresh = false}) async {
|
||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||
|
||||
final options = refresh
|
||||
? _cacheOptions.copyWith(policy: CachePolicy.refreshForceCache).toExtra()
|
||||
: _cacheOptions.toExtra();
|
||||
|
||||
try {
|
||||
final categoryMap = await _getVodCategories();
|
||||
|
||||
@@ -245,7 +253,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_vod_streams',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> vods = response.data as List<dynamic>;
|
||||
@@ -283,7 +291,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_series_categories',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> categories = response.data as List<dynamic>;
|
||||
@@ -303,9 +311,13 @@ class XtreamService {
|
||||
}
|
||||
|
||||
/// Get all series grouped by category
|
||||
Future<Map<String, List<Series>>> getSeries() async {
|
||||
Future<Map<String, List<Series>>> getSeries({bool refresh = false}) async {
|
||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||
|
||||
final options = refresh
|
||||
? _cacheOptions.copyWith(policy: CachePolicy.refreshForceCache).toExtra()
|
||||
: _cacheOptions.toExtra();
|
||||
|
||||
try {
|
||||
final categoryMap = await _getSeriesCategories();
|
||||
|
||||
@@ -316,7 +328,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_series',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> seriesList = response.data as List<dynamic>;
|
||||
@@ -358,7 +370,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_vod_streams',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> allMovies = response.data as List<dynamic>;
|
||||
@@ -397,7 +409,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_vod_streams',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> allMovies = response.data as List<dynamic>;
|
||||
@@ -438,7 +450,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_series',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> allSeries = response.data as List<dynamic>;
|
||||
@@ -477,7 +489,7 @@ class XtreamService {
|
||||
'password': _currentPlaylist!.password,
|
||||
'action': 'get_series',
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final List<dynamic> allSeries = response.data as List<dynamic>;
|
||||
@@ -515,7 +527,7 @@ class XtreamService {
|
||||
'action': 'get_series_info',
|
||||
'series_id': seriesId,
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
return xm.SeriesInfo.fromJson(response.data as Map<String, dynamic>);
|
||||
@@ -539,7 +551,7 @@ class XtreamService {
|
||||
'action': 'get_vod_info',
|
||||
'vod_id': vodId,
|
||||
},
|
||||
options: Options(extra: _cacheOptions.toExtra()),
|
||||
options: Options(extra: options),
|
||||
);
|
||||
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
|
||||
@@ -243,10 +243,12 @@
|
||||
}
|
||||
|
||||
const hls = new Hls({
|
||||
maxBufferLength: 60,
|
||||
maxMaxBufferLength: 120,
|
||||
maxBufferLength: 30, // 30s is plenty for desktop stability
|
||||
maxMaxBufferLength: 60,
|
||||
enableWorker: true,
|
||||
liveSyncDurationCount: 5
|
||||
liveSyncDurationCount: 3,
|
||||
liveMaxLatencyDurationCount: 10,
|
||||
lowLatencyMode: true
|
||||
});
|
||||
|
||||
window.hlsInstance = hls;
|
||||
@@ -281,8 +283,9 @@
|
||||
url: url
|
||||
}, {
|
||||
enableWorker: true,
|
||||
stashInitialSize: 1024 * 1024,
|
||||
enableStashBuffer: true
|
||||
stashInitialSize: 512 * 1024, // Smaller stash for faster start and better audio sync
|
||||
enableStashBuffer: true,
|
||||
lazyLoad: false
|
||||
});
|
||||
|
||||
player.attachMediaElement(video);
|
||||
|
||||
@@ -229,12 +229,12 @@
|
||||
if (isHLS && Hls.isSupported()) {
|
||||
log('Using Hls.js optimized for mobile');
|
||||
const hls = new Hls({
|
||||
maxBufferLength: 90,
|
||||
maxMaxBufferLength: 180,
|
||||
liveSyncDurationCount: 6,
|
||||
liveMaxLatencyDurationCount: 12,
|
||||
maxBufferLength: 30,
|
||||
maxMaxBufferLength: 60,
|
||||
liveSyncDurationCount: 3,
|
||||
liveMaxLatencyDurationCount: 10,
|
||||
enableWorker: true,
|
||||
lowLatencyMode: false
|
||||
lowLatencyMode: true
|
||||
});
|
||||
hls.loadSource(streamUrl);
|
||||
hls.attachMedia(video);
|
||||
|
||||
Reference in new issue
Block a user