feat(hls): add native HLS (m3u8) download protocol (#1515)

This commit is contained in:
sun authored and GitHub committed 2026-09-15 23:40:31 +08:00
1 parent e47a20a1bd
commit 46c34f2e92
56 files changed
+4536 -6

No files matched your search

@@ -56,6 +56,7 @@ class ProtocolConfig {
HttpConfig http = HttpConfig();
BtConfig bt = BtConfig();
Ed2kConfig ed2k = Ed2kConfig();
HlsConfig hls = HlsConfig();
ProtocolConfig();
@@ -65,6 +66,25 @@ class ProtocolConfig {
Map<String, dynamic> toJson() => _$ProtocolConfigToJson(this);
}
@JsonSerializable()
class HlsConfig {
int segmentConnections;
int maxRetries;
int timeoutSeconds;
bool prefetchContentLength;
HlsConfig({
this.segmentConnections = 0,
this.maxRetries = 0,
this.timeoutSeconds = 0,
this.prefetchContentLength = false,
});
factory HlsConfig.fromJson(Map<String, dynamic> json) => _$HlsConfigFromJson(json);
Map<String, dynamic> toJson() => _$HlsConfigToJson(this);
}
@JsonSerializable()
class HttpConfig {
String userAgent;
@@ -56,12 +56,28 @@ Map<String, dynamic> _$ApiServerConfigToJson(ApiServerConfig instance) => <Strin
ProtocolConfig _$ProtocolConfigFromJson(Map<String, dynamic> json) => ProtocolConfig()
..http = HttpConfig.fromJson(json['http'] as Map<String, dynamic>)
..bt = BtConfig.fromJson(json['bt'] as Map<String, dynamic>)
..ed2k = Ed2kConfig.fromJson(json['ed2k'] as Map<String, dynamic>);
..ed2k = Ed2kConfig.fromJson(json['ed2k'] as Map<String, dynamic>)
..hls = HlsConfig.fromJson(json['hls'] as Map<String, dynamic>);
Map<String, dynamic> _$ProtocolConfigToJson(ProtocolConfig instance) => <String, dynamic>{
'http': instance.http.toJson(),
'bt': instance.bt.toJson(),
'ed2k': instance.ed2k.toJson(),
'hls': instance.hls.toJson(),
};
HlsConfig _$HlsConfigFromJson(Map<String, dynamic> json) => HlsConfig(
segmentConnections: (json['segmentConnections'] as num?)?.toInt() ?? 0,
maxRetries: (json['maxRetries'] as num?)?.toInt() ?? 0,
timeoutSeconds: (json['timeoutSeconds'] as num?)?.toInt() ?? 0,
prefetchContentLength: json['prefetchContentLength'] as bool? ?? false,
);
Map<String, dynamic> _$HlsConfigToJson(HlsConfig instance) => <String, dynamic>{
'segmentConnections': instance.segmentConnections,
'maxRetries': instance.maxRetries,
'timeoutSeconds': instance.timeoutSeconds,
'prefetchContentLength': instance.prefetchContentLength,
};
HttpConfig _$HttpConfigFromJson(Map<String, dynamic> json) => HttpConfig(
+1 -1
View File
@@ -6,7 +6,7 @@ part 'task.g.dart';
enum Status { ready, running, pause, wait, error, done }
enum Protocol { http, bt, ed2k }
enum Protocol { http, bt, ed2k, hls }
// ExtractStatus enum matching Go backend
enum ExtractStatus {
+1 -1
View File
@@ -38,7 +38,7 @@ const _$StatusEnumMap = {
Status.done: 'done',
};
const _$ProtocolEnumMap = {Protocol.http: 'http', Protocol.bt: 'bt', Protocol.ed2k: 'ed2k'};
const _$ProtocolEnumMap = {Protocol.http: 'http', Protocol.bt: 'bt', Protocol.ed2k: 'ed2k', Protocol.hls: 'hls'};
Progress _$ProgressFromJson(Map<String, dynamic> json) => Progress(
used: (json['used'] as num).toInt(),
+1 -1
View File
@@ -55,7 +55,7 @@ sealed class TaskStats {
Protocol.http => HttpTaskStats.fromJson(snapshot),
Protocol.bt => BtTaskStats.fromJson(snapshot: snapshot, runtime: runtime),
Protocol.ed2k => Ed2kTaskStats.fromJson(snapshot: snapshot, runtime: runtime),
null => null,
Protocol.hls || null => null,
};
}
}
@@ -65,6 +65,8 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
final _maxRunningController = TextEditingController();
final _httpUserAgentController = TextEditingController();
final _httpConnectionsController = TextEditingController();
final _hlsConnectionsController = TextEditingController();
final _hlsMaxRetriesController = TextEditingController();
final _btListenPortController = TextEditingController();
final _btSeedRatioController = TextEditingController();
final _btSeedTimeController = TextEditingController();
@@ -96,6 +98,8 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
_maxRunningController,
_httpUserAgentController,
_httpConnectionsController,
_hlsConnectionsController,
_hlsMaxRetriesController,
_btListenPortController,
_btSeedRatioController,
_btSeedTimeController,
@@ -572,6 +576,32 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
],
),
),
_SettingsBlock(
key: const ValueKey('settings-hls-block'),
title: 'HLS',
child: _SettingsGroup(
children: [
SettingsItem(
title: context.l10n.connections,
child: _NumberSettingControl(
fieldKey: const ValueKey('hls-connections-input'),
controller: _hlsConnectionsController,
min: 1,
max: 256,
),
),
SettingsItem(
title: context.l10n.maxRetries,
child: _NumberSettingControl(
fieldKey: const ValueKey('hls-max-retries-input'),
controller: _hlsMaxRetriesController,
min: 0,
max: 30,
),
),
],
),
),
_SettingsBlock(
title: 'BitTorrent',
child: _SettingsGroup(
@@ -953,6 +983,8 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
_maxRunningController.text = _config!.maxRunning.clamp(1, 256).toString();
_httpUserAgentController.text = _config!.protocolConfig.http.userAgent;
_httpConnectionsController.text = _config!.protocolConfig.http.connections.clamp(1, 256).toString();
_hlsConnectionsController.text = _config!.protocolConfig.hls.segmentConnections.clamp(1, 256).toString();
_hlsMaxRetriesController.text = _config!.protocolConfig.hls.maxRetries.clamp(0, 30).toString();
_btListenPortController.text = _config!.protocolConfig.bt.listenPort.clamp(0, 65535).toString();
_btSeedRatioController.text = _config!.protocolConfig.bt.seedRatio.toString();
_btSeedTimeController.text = (_config!.protocolConfig.bt.seedTime ~/ 60).clamp(0, 100000000).toString();
@@ -1048,6 +1080,18 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
min: 1,
max: 256,
);
config.protocolConfig.hls.segmentConnections = _boundedInt(
_hlsConnectionsController,
fallback: config.protocolConfig.hls.segmentConnections,
min: 1,
max: 256,
);
config.protocolConfig.hls.maxRetries = _boundedInt(
_hlsMaxRetriesController,
fallback: config.protocolConfig.hls.maxRetries,
min: 0,
max: 30,
);
config.protocolConfig.bt.listenPort = _boundedInt(
_btListenPortController,
fallback: config.protocolConfig.bt.listenPort,
@@ -294,7 +294,7 @@ TaskAssetType _assetType(String name, {required bool isFolder, required api_task
return switch (protocol) {
api_task.Protocol.bt => TaskAssetType.torrent,
api_task.Protocol.ed2k => TaskAssetType.ed2k,
api_task.Protocol.http || null => TaskAssetType.file,
api_task.Protocol.hls || api_task.Protocol.http || null => TaskAssetType.file,
};
}
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "حفظ كمجلد التنزيل الافتراضي",
"downloadDirValid": "الرجاء تحديد مجلد التنزيل",
"connections": "الاتصالات",
"maxRetries": "الحد الأقصى لإعادة المحاولات",
"useServerCtime": "استخدام وقت الخادم لإنشاء الملف",
"maxRunning": "الحد الأقصى للمهام المتزامنة",
"defaultDirectDownload": "تفعيل التنزيل المباشر افتراضياً",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Standart endirmə qovluğu kimi yadda saxla",
"downloadDirValid": "Zəhmət olmasa endirmə qovluğunu seçin",
"connections": "Əlaqələr",
"maxRetries": "Maksimum cəhd sayı",
"useServerCtime": "Fayl yaratma tarixi üçün server vaxtından istifadə et",
"maxRunning": "Paralel Endirmələr",
"defaultDirectDownload": "Standart olaraq Birbaşa Endirmə",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Desa com a carpeta de baixades predeterminada",
"downloadDirValid": "Seleccioneu la carpeta de baixades",
"connections": "Connexions",
"maxRetries": "Reintents màxims",
"useServerCtime": "Utilitza l'hora del servidor per a la creació de fitxers",
"maxRunning": "Màxim de tasques en execució",
"defaultDirectDownload": "Activa la baixada directa per defecte",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Als Standard-Downloadordner speichern",
"downloadDirValid": "Bitte wählen Sie das Download-Verzeichnis",
"connections": "Anschlüsse",
"maxRetries": "Maximale Wiederholungsversuche",
"useServerCtime": "Serverzeit für die Dateierstellung verwenden",
"maxRunning": "Maximale Anzahl ausgeführter Aufgaben",
"defaultDirectDownload": "Direkten Download standardmäßig aktivieren",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Save as default download directory",
"downloadDirValid": "Please select the download directory",
"connections": "Connections",
"maxRetries": "Max retries",
"useServerCtime": "Use server time for file creation",
"maxRunning": "Concurrent Downloads",
"defaultDirectDownload": "Direct Download by Default",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Guardar como carpeta de descargas predeterminada",
"downloadDirValid": "Por favor, selecciona el directorio de descarga",
"connections": "Conexiones",
"maxRetries": "Reintentos máximos",
"useServerCtime": "Usar tiempo del servidor para la creación de archivos",
"maxRunning": "Máximo de Tareas en Ejecución",
"defaultDirectDownload": "Ver descarga directa por defecto",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "ذخیره به‌عنوان پوشه پیش‌فرض دانلود",
"downloadDirValid": "لطفا دایرکتوری دانلود را انتخاب کنید",
"connections": "اتصالات",
"maxRetries": "حداکثر تلاش مجدد",
"useServerCtime": "استفاده از زمان سرور برای ایجاد فایل",
"maxRunning": "حداکثر وظایف در حال اجرا",
"defaultDirectDownload": "دانلود مستقیم را به صورت پیش فرض بررسی کنید",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Enregistrer comme dossier de téléchargement par défaut",
"downloadDirValid": "Veuillez sélectionner le répertoire de téléchargement",
"connections": "Connexions",
"maxRetries": "Nombre maximal de tentatives",
"useServerCtime": "Utiliser l'heure du serveur pour la création de fichier",
"maxRunning": "Tâches en cours maximum",
"defaultDirectDownload": "Vérifiez le téléchargement direct par défaut",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Mentés alapértelmezett letöltési mappaként",
"downloadDirValid": "Kérjük, válassza ki a letöltési könyvtárat",
"connections": "Kapcsolatok",
"maxRetries": "Maximális újrapróbálkozások",
"useServerCtime": "Használja a szerveridőt a fájl létrehozásához",
"maxRunning": "Maximális futó feladatok",
"defaultDirectDownload": "Alapértelmezettként ellenőrizze a közvetlen letöltést",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Simpan sebagai folder unduhan default",
"downloadDirValid": "Silahkan pilih direktori unduhan",
"connections": "Koneksi",
"maxRetries": "Percobaan ulang maks.",
"useServerCtime": "Gunakan waktu server untuk pembuatan file",
"maxRunning": "Tugas Maksimum yang Berjalan",
"defaultDirectDownload": "Pilih unduhan langsung sebagai opsi standar",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Salva come cartella di download predefinita",
"downloadDirValid": "Seleziona la directory di scaricamento",
"connections": "Connessioni",
"maxRetries": "Tentativi massimi",
"useServerCtime": "Utilizza l'ora del server per la creazione dei file",
"maxRunning": "Numero massimo di attività in esecuzione",
"defaultDirectDownload": "Controlla il download diretto per impostazione predefinita",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "デフォルトのダウンロード先として保存",
"downloadDirValid": "ダウンロードディレクトリを選択してください",
"connections": "接続",
"maxRetries": "最大リトライ回数",
"useServerCtime": "ファイル作成時にサーバー時間を使用する",
"maxRunning": "最大実行タスク",
"defaultDirectDownload": "デフォルトで直接ダウンロードにチェックを入れる",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "기본 다운로드 폴더로 저장",
"downloadDirValid": "다운로드 폴더를 선택해 주세요",
"connections": "연결 수",
"maxRetries": "최대 재시도 횟수",
"useServerCtime": "파일 생성 시간에 서버 시간 사용",
"maxRunning": "최대 동시 작업 수",
"defaultDirectDownload": "기본으로 바로 다운로드 사용",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Zapisz jako domyślny folder pobierania",
"downloadDirValid": "Wskaż folder pobierania",
"connections": "Połączenia",
"maxRetries": "Maks. liczba ponownych prób",
"useServerCtime": "Użyj czasu serwera do utworzenia pliku",
"maxRunning": "Maksymalna liczba zadań",
"defaultDirectDownload": "Domyślnie sprawdź bezpośredni pobór",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Salvar como pasta de download padrão",
"downloadDirValid": "Por favor, selecione a pasta",
"connections": "Conexões",
"maxRetries": "Tentativas máximas",
"useServerCtime": "Usar o tempo de servidor para a criação de ficheiros",
"maxRunning": "Máximo de Tarefas Simultâneas",
"defaultDirectDownload": "Marcar download direto por padrão",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Сохранить как папку загрузок по умолчанию",
"downloadDirValid": "Пожалуйста, выберите папку для загрузки",
"connections": "Соединений",
"maxRetries": "Макс. число повторов",
"useServerCtime": "Использовать время сервера для создания файла",
"maxRunning": "Максимальное количество активных задач",
"defaultDirectDownload": "Прямая загрузка по умолчанию",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "இயல்புநிலை பதிவிறக்க கோப்புறையாக சேமி",
"downloadDirValid": "பதிவிரக்குமிடத்தை தேர்வு செயுங்கள்",
"connections": "தொடர்புகள்",
"maxRetries": "அதிகபட்ச மறுமுயற்சிகள்",
"useServerCtime": "கோப்பு உருவாக்க சேவையக நேரத்தைப் பயன்படுத்தவும்",
"maxRunning": "அதிகபட்ச இயங்கும் பணிகள்",
"defaultDirectDownload": "இயல்புநிலையாக நேரடிப் பதிவிறக்கத்தைச் சரிபார்க்கவும்",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Varsayılan indirme klasörü olarak kaydet",
"downloadDirValid": "Lütfen indirme dizinini seçin",
"connections": "Bağlantılar",
"maxRetries": "Maksimum yeniden deneme",
"useServerCtime": "Dosya oluşturma tarihi için sunucu zamanını kullan",
"maxRunning": "Maksimum Çalışan İşlem",
"defaultDirectDownload": "Varsayılan olarak direkt indirmeyi işaretle",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Зберегти як типову папку завантажень",
"downloadDirValid": "Будь ласка, виберіть каталог завантажень",
"connections": "З’єднання",
"maxRetries": "Макс. кількість повторів",
"useServerCtime": "Використовувати час сервера для створення файлу",
"maxRunning": "Макс. завдань у черзі",
"defaultDirectDownload": "Пряме завантаження за замовчуванням",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "Lưu làm thư mục tải xuống mặc định",
"downloadDirValid": "Vui lòng chọn thư mục tải về",
"connections": "Kết nối",
"maxRetries": "Số lần thử lại tối đa",
"useServerCtime": "Sử dụng thời gian máy chủ cho việc tạo tệp",
"maxRunning": "Số nhiệm vụ tối đa",
"defaultDirectDownload": "Kiểm tra trực tiếp tải về theo mặc định",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "保存为默认下载目录",
"downloadDirValid": "请选择下载目录",
"connections": "连接数",
"maxRetries": "最大重试次数",
"useServerCtime": "创建文件使用服务器时间",
"maxRunning": "最大下载数",
"defaultDirectDownload": "默认启用直接下载",
+1
View File
@@ -38,6 +38,7 @@
"rememberLastDownloadDir": "儲存為預設下載目錄",
"downloadDirValid": "請選擇下載目錄",
"connections": "連接數",
"maxRetries": "最大重試次數",
"useServerCtime": "使用伺服器時間作為檔案建立時間",
"maxRunning": "最大執行任務數",
"defaultDirectDownload": "預設勾選直接下載",