feat(streaming): buffer resilience and automatic reconnection

- Construct the audio player with an enlarged live-stream buffer (15-50s forward cushion, 2.5s to start, 5s after rebuffer) so short network drops play through silently
- Add reconnect-on-stall state machine with bounded exponential backoff (1/2/4/8/16s, ~90s total window, 5 attempts) that re-prepares to the live edge; backoff/decision logic extracted to controlador_reconexion.dart as pure testable code
- Surface a new reconnecting playback state in the mini player and full player (localized in all 13 locales) instead of error dialogs during the retry window; a single friendly error appears only after exhaustion
- Guard interplay: user pause/stop cancels retries, audio interruptions cancel reconnect, alarm wake-up path keeps precedence, recording fails cleanly during drops
- Reset retry budget on station change; route stream timeouts through the network-error class
- 10 new tests (99 total green), flutter analyze clean
This commit is contained in:
2026-06-11 19:54:30 +02:00
parent 079e19f0ee
commit 0380bbb1e7
38 changed files with 743 additions and 38 deletions
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "جارٍ الاتصال...",
"playbackStatusLive": "مباشر",
"playbackStatusPaused": "متوقف مؤقتًا",
"playbackStatusReconnecting": "جارٍ إعادة الاتصال...",
"playbackStatusConnectionError": "خطأ في الاتصال",
"playbackStatusStopped": "متوقف",
"stationSemanticLabel": "محطة {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "সংযুক্ত হচ্ছে...",
"playbackStatusLive": "লাইভ",
"playbackStatusPaused": "বিরতিতে",
"playbackStatusReconnecting": "পুনরায় সংযোগ করা হচ্ছে...",
"playbackStatusConnectionError": "সংযোগে ত্রুটি",
"playbackStatusStopped": "বন্ধ",
"stationSemanticLabel": "স্টেশন {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "Verbindung wird hergestellt...",
"playbackStatusLive": "Live",
"playbackStatusPaused": "Pausiert",
"playbackStatusReconnecting": "Wird neu verbunden...",
"playbackStatusConnectionError": "Verbindungsfehler",
"playbackStatusStopped": "Gestoppt",
"stationSemanticLabel": "Sender {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "Connecting...",
"playbackStatusLive": "Live",
"playbackStatusPaused": "Paused",
"playbackStatusReconnecting": "Reconnecting...",
"playbackStatusConnectionError": "Connection error",
"playbackStatusStopped": "Stopped",
"stationSemanticLabel": "Station {stationName}",
+1
View File
@@ -473,6 +473,7 @@
"playbackStatusConnecting": "Conectando...",
"playbackStatusLive": "En directo",
"playbackStatusPaused": "Pausado",
"playbackStatusReconnecting": "Reconectando...",
"playbackStatusConnectionError": "Error de conexión",
"playbackStatusStopped": "Detenido",
"stationSemanticLabel": "Emisora {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "Connexion...",
"playbackStatusLive": "En direct",
"playbackStatusPaused": "En pause",
"playbackStatusReconnecting": "Reconnexion...",
"playbackStatusConnectionError": "Erreur de connexion",
"playbackStatusStopped": "Arrêté",
"stationSemanticLabel": "Station {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "कनेक्ट हो रहा है...",
"playbackStatusLive": "लाइव",
"playbackStatusPaused": "विराम पर",
"playbackStatusReconnecting": "पुनः कनेक्ट हो रहा है...",
"playbackStatusConnectionError": "कनेक्शन त्रुटि",
"playbackStatusStopped": "बंद",
"stationSemanticLabel": "स्टेशन {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "Menghubungkan...",
"playbackStatusLive": "Siaran langsung",
"playbackStatusPaused": "Dijeda",
"playbackStatusReconnecting": "Menyambung ulang...",
"playbackStatusConnectionError": "Kesalahan koneksi",
"playbackStatusStopped": "Dihentikan",
"stationSemanticLabel": "Stasiun {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "Connessione...",
"playbackStatusLive": "In diretta",
"playbackStatusPaused": "In pausa",
"playbackStatusReconnecting": "Riconnessione...",
"playbackStatusConnectionError": "Errore di connessione",
"playbackStatusStopped": "Interrotto",
"stationSemanticLabel": "Stazione {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "接続中...",
"playbackStatusLive": "ライブ",
"playbackStatusPaused": "一時停止中",
"playbackStatusReconnecting": "再接続中...",
"playbackStatusConnectionError": "接続エラー",
"playbackStatusStopped": "停止中",
"stationSemanticLabel": "ラジオ局 {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "Conectando...",
"playbackStatusLive": "Ao vivo",
"playbackStatusPaused": "Pausado",
"playbackStatusReconnecting": "Reconectando...",
"playbackStatusConnectionError": "Erro de conexão",
"playbackStatusStopped": "Parado",
"stationSemanticLabel": "Estação {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "Подключение...",
"playbackStatusLive": "В эфире",
"playbackStatusPaused": "Приостановлено",
"playbackStatusReconnecting": "Переподключение...",
"playbackStatusConnectionError": "Ошибка подключения",
"playbackStatusStopped": "Остановлено",
"stationSemanticLabel": "Станция {stationName}",
+1
View File
@@ -477,6 +477,7 @@
"playbackStatusConnecting": "正在连接...",
"playbackStatusLive": "直播中",
"playbackStatusPaused": "已暂停",
"playbackStatusReconnecting": "正在重新连接...",
"playbackStatusConnectionError": "连接错误",
"playbackStatusStopped": "已停止",
"stationSemanticLabel": "电台 {stationName}",
+6
View File
@@ -1730,6 +1730,12 @@ abstract class AppLocalizations {
/// **'Pausado'**
String get playbackStatusPaused;
/// No description provided for @playbackStatusReconnecting.
///
/// In es, this message translates to:
/// **'Reconectando...'**
String get playbackStatusReconnecting;
/// No description provided for @playbackStatusConnectionError.
///
/// In es, this message translates to:
+3
View File
@@ -919,6 +919,9 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get playbackStatusPaused => 'متوقف مؤقتًا';
@override
String get playbackStatusReconnecting => 'جارٍ إعادة الاتصال...';
@override
String get playbackStatusConnectionError => 'خطأ في الاتصال';
+3
View File
@@ -928,6 +928,9 @@ class AppLocalizationsBn extends AppLocalizations {
@override
String get playbackStatusPaused => 'বিরতিতে';
@override
String get playbackStatusReconnecting => 'পুনরায় সংযোগ করা হচ্ছে...';
@override
String get playbackStatusConnectionError => 'সংযোগে ত্রুটি';
+3
View File
@@ -930,6 +930,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get playbackStatusPaused => 'Pausiert';
@override
String get playbackStatusReconnecting => 'Wird neu verbunden...';
@override
String get playbackStatusConnectionError => 'Verbindungsfehler';
+3
View File
@@ -923,6 +923,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get playbackStatusPaused => 'Paused';
@override
String get playbackStatusReconnecting => 'Reconnecting...';
@override
String get playbackStatusConnectionError => 'Connection error';
+3
View File
@@ -927,6 +927,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get playbackStatusPaused => 'Pausado';
@override
String get playbackStatusReconnecting => 'Reconectando...';
@override
String get playbackStatusConnectionError => 'Error de conexión';
+3
View File
@@ -933,6 +933,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get playbackStatusPaused => 'En pause';
@override
String get playbackStatusReconnecting => 'Reconnexion...';
@override
String get playbackStatusConnectionError => 'Erreur de connexion';
+3
View File
@@ -924,6 +924,9 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get playbackStatusPaused => 'विराम पर';
@override
String get playbackStatusReconnecting => 'पुनः कनेक्ट हो रहा है...';
@override
String get playbackStatusConnectionError => 'कनेक्शन त्रुटि';
+3
View File
@@ -928,6 +928,9 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get playbackStatusPaused => 'Dijeda';
@override
String get playbackStatusReconnecting => 'Menyambung ulang...';
@override
String get playbackStatusConnectionError => 'Kesalahan koneksi';
+3
View File
@@ -930,6 +930,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get playbackStatusPaused => 'In pausa';
@override
String get playbackStatusReconnecting => 'Riconnessione...';
@override
String get playbackStatusConnectionError => 'Errore di connessione';
+3
View File
@@ -895,6 +895,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get playbackStatusPaused => '一時停止中';
@override
String get playbackStatusReconnecting => '再接続中...';
@override
String get playbackStatusConnectionError => '接続エラー';
+3
View File
@@ -925,6 +925,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get playbackStatusPaused => 'Pausado';
@override
String get playbackStatusReconnecting => 'Reconectando...';
@override
String get playbackStatusConnectionError => 'Erro de conexão';
+3
View File
@@ -929,6 +929,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get playbackStatusPaused => 'Приостановлено';
@override
String get playbackStatusReconnecting => 'Переподключение...';
@override
String get playbackStatusConnectionError => 'Ошибка подключения';
+3
View File
@@ -891,6 +891,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get playbackStatusPaused => '已暂停';
@override
String get playbackStatusReconnecting => '正在重新连接...';
@override
String get playbackStatusConnectionError => '连接错误';