diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 0dfd1d5c..8e138bcc 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -643,7 +643,8 @@ "joinInstructions": "Gib den vom Host geteilten Sitzungscode ein, um seiner Schausitzung beizutreten.", "failedToCreate": "Sitzung konnte nicht erstellt werden", "failedToJoin": "Sitzung konnte nicht beigetreten werden", - "sessionCodeCopied": "Sitzungscode in Zwischenablage kopiert" + "sessionCodeCopied": "Sitzungscode in Zwischenablage kopiert", + "relayUnreachable": "Der Relay-Server ist nicht erreichbar. Möglicherweise blockiert dein Internetanbieter die Verbindung. Du kannst es trotzdem versuchen, aber Watch Together funktioniert eventuell nicht." }, "shaders": { "title": "Shader", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 81c35990..8c2fb28b 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -623,7 +623,8 @@ "joinInstructions": "Enter the session code shared by the host to join their watch session.", "failedToCreate": "Failed to create session", "failedToJoin": "Failed to join session", - "sessionCodeCopied": "Session code copied to clipboard" + "sessionCodeCopied": "Session code copied to clipboard", + "relayUnreachable": "The relay server is unreachable. This may be caused by your ISP blocking the connection. You can still try, but Watch Together may not work." }, "downloads": { "title": "Downloads", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 97cdf3bd..2446edb3 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -623,7 +623,8 @@ "joinInstructions": "Introduce el código de sesión compartido por el anfitrión para unirte a su sesión.", "failedToCreate": "Error al crear la sesión", "failedToJoin": "Error al unirse a la sesión", - "sessionCodeCopied": "Código de sesión copiado al portapapeles" + "sessionCodeCopied": "Código de sesión copiado al portapapeles", + "relayUnreachable": "El servidor de retransmisión no está disponible. Esto puede deberse a que tu proveedor de internet bloquea la conexión. Puedes intentarlo de todos modos, pero Watch Together podría no funcionar." }, "downloads": { "title": "Descargas", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index c7f1d866..f74ec1b1 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -623,7 +623,8 @@ "joinInstructions": "Entrez le code de session partagé par l'hôte pour rejoindre sa session de visionnage.", "failedToCreate": "Échec de la création de la session", "failedToJoin": "Échec de la connexion à la session", - "sessionCodeCopied": "Code de session copié dans le presse-papiers" + "sessionCodeCopied": "Code de session copié dans le presse-papiers", + "relayUnreachable": "Le serveur relais est inaccessible. Cela peut être dû au blocage de la connexion par votre fournisseur d'accès. Vous pouvez quand même essayer, mais Watch Together pourrait ne pas fonctionner." }, "downloads": { "title": "Téléchargements", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 1ec1f750..2f5a6fdc 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -643,7 +643,8 @@ "joinInstructions": "Inserisci il codice sessione condiviso dall'host per unirti alla loro sessione di visione.", "failedToCreate": "Impossibile creare la sessione", "failedToJoin": "Impossibile unirsi alla sessione", - "sessionCodeCopied": "Codice sessione copiato negli appunti" + "sessionCodeCopied": "Codice sessione copiato negli appunti", + "relayUnreachable": "Il server di inoltro non è raggiungibile. Questo potrebbe essere causato dal blocco della connessione da parte del tuo provider. Puoi comunque provare, ma Watch Together potrebbe non funzionare." }, "shaders": { "title": "Shader", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 083b729b..5bd4ee33 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -623,7 +623,8 @@ "joinInstructions": "호스트가 공유한 세션 코드를 입력하여 시청 세션에 참여하세요.", "failedToCreate": "세션 생성 실패", "failedToJoin": "세션 참여 실패", - "sessionCodeCopied": "세션 코드가 클립보드에 복사되었습니다" + "sessionCodeCopied": "세션 코드가 클립보드에 복사되었습니다", + "relayUnreachable": "릴레이 서버에 연결할 수 없습니다. 인터넷 제공업체가 연결을 차단하고 있을 수 있습니다. 시도해 볼 수 있지만 함께 보기가 작동하지 않을 수 있습니다." }, "downloads": { "title": "다운로드", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 28e8d250..670e6842 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -643,7 +643,8 @@ "joinInstructions": "Voer de sessiecode in die door de host is gedeeld om deel te nemen aan hun kijksessie.", "failedToCreate": "Sessie maken mislukt", "failedToJoin": "Sessie deelnemen mislukt", - "sessionCodeCopied": "Sessiecode gekopieerd naar klembord" + "sessionCodeCopied": "Sessiecode gekopieerd naar klembord", + "relayUnreachable": "De relayserver is niet bereikbaar. Dit kan worden veroorzaakt doordat je internetprovider de verbinding blokkeert. Je kunt het toch proberen, maar Watch Together werkt mogelijk niet." }, "shaders": { "title": "Shaders", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 87bdff29..3e134b34 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 9 -/// Strings: 6048 (672 per locale) +/// Strings: 6057 (673 per locale) /// -/// Built on 2026-02-10 at 20:34 UTC +/// Built on 2026-02-10 at 21:37 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 8e4bf85f..acad49cc 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -904,6 +904,7 @@ class _TranslationsWatchTogetherDe implements TranslationsWatchTogetherEn { @override String get failedToCreate => 'Sitzung konnte nicht erstellt werden'; @override String get failedToJoin => 'Sitzung konnte nicht beigetreten werden'; @override String get sessionCodeCopied => 'Sitzungscode in Zwischenablage kopiert'; + @override String get relayUnreachable => 'Der Relay-Server ist nicht erreichbar. Möglicherweise blockiert dein Internetanbieter die Verbindung. Du kannst es trotzdem versuchen, aber Watch Together funktioniert eventuell nicht.'; } // Path: shaders @@ -1715,6 +1716,7 @@ extension on TranslationsDe { 'watchTogether.failedToCreate' => 'Sitzung konnte nicht erstellt werden', 'watchTogether.failedToJoin' => 'Sitzung konnte nicht beigetreten werden', 'watchTogether.sessionCodeCopied' => 'Sitzungscode in Zwischenablage kopiert', + 'watchTogether.relayUnreachable' => 'Der Relay-Server ist nicht erreichbar. Möglicherweise blockiert dein Internetanbieter die Verbindung. Du kannst es trotzdem versuchen, aber Watch Together funktioniert eventuell nicht.', 'shaders.title' => 'Shader', 'shaders.noShaderDescription' => 'Keine Videoverbesserung', 'shaders.nvscalerDescription' => 'NVIDIA-Bildskalierung für schärferes Video', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index a516022e..b1da3b8d 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -1927,6 +1927,9 @@ class TranslationsWatchTogetherEn { /// en: 'Session code copied to clipboard' String get sessionCodeCopied => 'Session code copied to clipboard'; + + /// en: 'The relay server is unreachable. This may be caused by your ISP blocking the connection. You can still try, but Watch Together may not work.' + String get relayUnreachable => 'The relay server is unreachable. This may be caused by your ISP blocking the connection. You can still try, but Watch Together may not work.'; } // Path: downloads @@ -3048,6 +3051,7 @@ extension on Translations { 'watchTogether.failedToCreate' => 'Failed to create session', 'watchTogether.failedToJoin' => 'Failed to join session', 'watchTogether.sessionCodeCopied' => 'Session code copied to clipboard', + 'watchTogether.relayUnreachable' => 'The relay server is unreachable. This may be caused by your ISP blocking the connection. You can still try, but Watch Together may not work.', 'downloads.title' => 'Downloads', 'downloads.manage' => 'Manage', 'downloads.tvShows' => 'TV Shows', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 9d4ab06f..7ae7bcaf 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -877,6 +877,7 @@ class _TranslationsWatchTogetherEs implements TranslationsWatchTogetherEn { @override String get failedToCreate => 'Error al crear la sesión'; @override String get failedToJoin => 'Error al unirse a la sesión'; @override String get sessionCodeCopied => 'Código de sesión copiado al portapapeles'; + @override String get relayUnreachable => 'El servidor de retransmisión no está disponible. Esto puede deberse a que tu proveedor de internet bloquea la conexión. Puedes intentarlo de todos modos, pero Watch Together podría no funcionar.'; } // Path: downloads @@ -1697,6 +1698,7 @@ extension on TranslationsEs { 'watchTogether.failedToCreate' => 'Error al crear la sesión', 'watchTogether.failedToJoin' => 'Error al unirse a la sesión', 'watchTogether.sessionCodeCopied' => 'Código de sesión copiado al portapapeles', + 'watchTogether.relayUnreachable' => 'El servidor de retransmisión no está disponible. Esto puede deberse a que tu proveedor de internet bloquea la conexión. Puedes intentarlo de todos modos, pero Watch Together podría no funcionar.', 'downloads.title' => 'Descargas', 'downloads.manage' => 'Gestionar', 'downloads.tvShows' => 'Series de TV', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 0a94a75e..1f12f72f 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -877,6 +877,7 @@ class _TranslationsWatchTogetherFr implements TranslationsWatchTogetherEn { @override String get failedToCreate => 'Échec de la création de la session'; @override String get failedToJoin => 'Échec de la connexion à la session'; @override String get sessionCodeCopied => 'Code de session copié dans le presse-papiers'; + @override String get relayUnreachable => 'Le serveur relais est inaccessible. Cela peut être dû au blocage de la connexion par votre fournisseur d\'accès. Vous pouvez quand même essayer, mais Watch Together pourrait ne pas fonctionner.'; } // Path: downloads @@ -1697,6 +1698,7 @@ extension on TranslationsFr { 'watchTogether.failedToCreate' => 'Échec de la création de la session', 'watchTogether.failedToJoin' => 'Échec de la connexion à la session', 'watchTogether.sessionCodeCopied' => 'Code de session copié dans le presse-papiers', + 'watchTogether.relayUnreachable' => 'Le serveur relais est inaccessible. Cela peut être dû au blocage de la connexion par votre fournisseur d\'accès. Vous pouvez quand même essayer, mais Watch Together pourrait ne pas fonctionner.', 'downloads.title' => 'Téléchargements', 'downloads.manage' => 'Gérer', 'downloads.tvShows' => 'Show TV', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index ad040c3e..c0fd9d5a 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -904,6 +904,7 @@ class _TranslationsWatchTogetherIt implements TranslationsWatchTogetherEn { @override String get failedToCreate => 'Impossibile creare la sessione'; @override String get failedToJoin => 'Impossibile unirsi alla sessione'; @override String get sessionCodeCopied => 'Codice sessione copiato negli appunti'; + @override String get relayUnreachable => 'Il server di inoltro non è raggiungibile. Questo potrebbe essere causato dal blocco della connessione da parte del tuo provider. Puoi comunque provare, ma Watch Together potrebbe non funzionare.'; } // Path: shaders @@ -1715,6 +1716,7 @@ extension on TranslationsIt { 'watchTogether.failedToCreate' => 'Impossibile creare la sessione', 'watchTogether.failedToJoin' => 'Impossibile unirsi alla sessione', 'watchTogether.sessionCodeCopied' => 'Codice sessione copiato negli appunti', + 'watchTogether.relayUnreachable' => 'Il server di inoltro non è raggiungibile. Questo potrebbe essere causato dal blocco della connessione da parte del tuo provider. Puoi comunque provare, ma Watch Together potrebbe non funzionare.', 'shaders.title' => 'Shader', 'shaders.noShaderDescription' => 'Nessun miglioramento video', 'shaders.nvscalerDescription' => 'Ridimensionamento NVIDIA per video più nitido', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 336d3079..4b17587a 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -877,6 +877,7 @@ class _TranslationsWatchTogetherKo implements TranslationsWatchTogetherEn { @override String get failedToCreate => '세션 생성 실패'; @override String get failedToJoin => '세션 참여 실패'; @override String get sessionCodeCopied => '세션 코드가 클립보드에 복사되었습니다'; + @override String get relayUnreachable => '릴레이 서버에 연결할 수 없습니다. 인터넷 제공업체가 연결을 차단하고 있을 수 있습니다. 시도해 볼 수 있지만 함께 보기가 작동하지 않을 수 있습니다.'; } // Path: downloads @@ -1697,6 +1698,7 @@ extension on TranslationsKo { 'watchTogether.failedToCreate' => '세션 생성 실패', 'watchTogether.failedToJoin' => '세션 참여 실패', 'watchTogether.sessionCodeCopied' => '세션 코드가 클립보드에 복사되었습니다', + 'watchTogether.relayUnreachable' => '릴레이 서버에 연결할 수 없습니다. 인터넷 제공업체가 연결을 차단하고 있을 수 있습니다. 시도해 볼 수 있지만 함께 보기가 작동하지 않을 수 있습니다.', 'downloads.title' => '다운로드', 'downloads.manage' => '관리', 'downloads.tvShows' => 'TV 프로그램', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index ddbbcbad..4c8468c0 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -904,6 +904,7 @@ class _TranslationsWatchTogetherNl implements TranslationsWatchTogetherEn { @override String get failedToCreate => 'Sessie maken mislukt'; @override String get failedToJoin => 'Sessie deelnemen mislukt'; @override String get sessionCodeCopied => 'Sessiecode gekopieerd naar klembord'; + @override String get relayUnreachable => 'De relayserver is niet bereikbaar. Dit kan worden veroorzaakt doordat je internetprovider de verbinding blokkeert. Je kunt het toch proberen, maar Watch Together werkt mogelijk niet.'; } // Path: shaders @@ -1715,6 +1716,7 @@ extension on TranslationsNl { 'watchTogether.failedToCreate' => 'Sessie maken mislukt', 'watchTogether.failedToJoin' => 'Sessie deelnemen mislukt', 'watchTogether.sessionCodeCopied' => 'Sessiecode gekopieerd naar klembord', + 'watchTogether.relayUnreachable' => 'De relayserver is niet bereikbaar. Dit kan worden veroorzaakt doordat je internetprovider de verbinding blokkeert. Je kunt het toch proberen, maar Watch Together werkt mogelijk niet.', 'shaders.title' => 'Shaders', 'shaders.noShaderDescription' => 'Geen videoverbetering', 'shaders.nvscalerDescription' => 'NVIDIA-beeldschaling voor scherpere video', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 17758d85..0e9ece85 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -904,6 +904,7 @@ class _TranslationsWatchTogetherSv implements TranslationsWatchTogetherEn { @override String get failedToCreate => 'Det gick inte att skapa session'; @override String get failedToJoin => 'Det gick inte att gå med i session'; @override String get sessionCodeCopied => 'Sessionskod kopierad till urklipp'; + @override String get relayUnreachable => 'Reläservern kan inte nås. Detta kan bero på att din internetleverantör blockerar anslutningen. Du kan fortfarande försöka, men Watch Together kanske inte fungerar.'; } // Path: shaders @@ -1715,6 +1716,7 @@ extension on TranslationsSv { 'watchTogether.failedToCreate' => 'Det gick inte att skapa session', 'watchTogether.failedToJoin' => 'Det gick inte att gå med i session', 'watchTogether.sessionCodeCopied' => 'Sessionskod kopierad till urklipp', + 'watchTogether.relayUnreachable' => 'Reläservern kan inte nås. Detta kan bero på att din internetleverantör blockerar anslutningen. Du kan fortfarande försöka, men Watch Together kanske inte fungerar.', 'shaders.title' => 'Shaders', 'shaders.noShaderDescription' => 'Ingen videoförbättring', 'shaders.nvscalerDescription' => 'NVIDIA-bildskalning för skarpare video', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index 17d06b14..43b19347 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -904,6 +904,7 @@ class _TranslationsWatchTogetherZh implements TranslationsWatchTogetherEn { @override String get failedToCreate => '创建会话失败'; @override String get failedToJoin => '加入会话失败'; @override String get sessionCodeCopied => '会话代码已复制到剪贴板'; + @override String get relayUnreachable => '无法连接到中继服务器。这可能是由于您的网络运营商屏蔽了连接。您仍然可以尝试,但一起观看功能可能无法正常使用。'; } // Path: shaders @@ -1715,6 +1716,7 @@ extension on TranslationsZh { 'watchTogether.failedToCreate' => '创建会话失败', 'watchTogether.failedToJoin' => '加入会话失败', 'watchTogether.sessionCodeCopied' => '会话代码已复制到剪贴板', + 'watchTogether.relayUnreachable' => '无法连接到中继服务器。这可能是由于您的网络运营商屏蔽了连接。您仍然可以尝试,但一起观看功能可能无法正常使用。', 'shaders.title' => '着色器', 'shaders.noShaderDescription' => '无视频增强', 'shaders.nvscalerDescription' => 'NVIDIA 图像缩放,使视频更清晰', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index ddf941a7..6b78e583 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -643,7 +643,8 @@ "joinInstructions": "Ange sessionskoden som delats av värden för att gå med i deras tittarsession.", "failedToCreate": "Det gick inte att skapa session", "failedToJoin": "Det gick inte att gå med i session", - "sessionCodeCopied": "Sessionskod kopierad till urklipp" + "sessionCodeCopied": "Sessionskod kopierad till urklipp", + "relayUnreachable": "Reläservern kan inte nås. Detta kan bero på att din internetleverantör blockerar anslutningen. Du kan fortfarande försöka, men Watch Together kanske inte fungerar." }, "shaders": { "title": "Shaders", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 73f93cb1..4c04d7c7 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -643,7 +643,8 @@ "joinInstructions": "输入主持人分享的会话代码以加入他们的观看会话。", "failedToCreate": "创建会话失败", "failedToJoin": "加入会话失败", - "sessionCodeCopied": "会话代码已复制到剪贴板" + "sessionCodeCopied": "会话代码已复制到剪贴板", + "relayUnreachable": "无法连接到中继服务器。这可能是由于您的网络运营商屏蔽了连接。您仍然可以尝试,但一起观看功能可能无法正常使用。" }, "shaders": { "title": "着色器", diff --git a/lib/watch_together/screens/watch_together_screen.dart b/lib/watch_together/screens/watch_together_screen.dart index 846c42ac..06a993d6 100644 --- a/lib/watch_together/screens/watch_together_screen.dart +++ b/lib/watch_together/screens/watch_together_screen.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -9,6 +11,7 @@ import '../../utils/dialogs.dart'; import '../../widgets/focused_scroll_scaffold.dart'; import '../models/watch_session.dart'; import '../providers/watch_together_provider.dart'; +import '../services/watch_together_peer_service.dart'; import '../widgets/join_session_dialog.dart'; /// Main screen for Watch Together functionality @@ -71,6 +74,30 @@ class _NotInSessionView extends StatefulWidget { class _NotInSessionViewState extends State<_NotInSessionView> { bool _isCreating = false; bool _isJoining = false; + bool? _healthOk; + + @override + void initState() { + super.initState(); + _checkHealth(); + } + + Future _checkHealth() async { + try { + final client = HttpClient(); + client.connectionTimeout = const Duration(seconds: 5); + final request = await client.getUrl(Uri.parse(WatchTogetherPeerService.healthUrl)); + final response = await request.close().timeout(const Duration(seconds: 5)); + final body = await response.transform(const SystemEncoding().decoder).join(); + client.close(); + if (!mounted) return; + setState(() => _healthOk = response.statusCode == 200 && body.trim() == 'ok'); + } catch (e) { + appLogger.w('Watch Together health check failed', error: e); + if (!mounted) return; + setState(() => _healthOk = false); + } + } @override Widget build(BuildContext context) { @@ -93,6 +120,27 @@ class _NotInSessionViewState extends State<_NotInSessionView> { style: theme.textTheme.bodyLarge?.copyWith(color: theme.colorScheme.onSurfaceVariant), textAlign: TextAlign.center, ), + if (_healthOk == false) ...[ + const SizedBox(height: 24), + Card( + color: theme.colorScheme.errorContainer, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Icon(Symbols.warning_rounded, color: theme.colorScheme.onErrorContainer), + const SizedBox(width: 12), + Expanded( + child: Text( + t.watchTogether.relayUnreachable, + style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onErrorContainer), + ), + ), + ], + ), + ), + ), + ], const SizedBox(height: 48), SizedBox( width: double.infinity, diff --git a/lib/watch_together/services/watch_together_peer_service.dart b/lib/watch_together/services/watch_together_peer_service.dart index 5014fd79..11bf0708 100644 --- a/lib/watch_together/services/watch_together_peer_service.dart +++ b/lib/watch_together/services/watch_together_peer_service.dart @@ -30,6 +30,8 @@ class PeerError { /// - Sending/receiving sync messages through the relay server /// - Reconnection on WebSocket drops class WatchTogetherPeerService { + static const String _baseUrl = 'https://ice.plezy.app'; + static String get healthUrl => '$_baseUrl/health'; static const String _relayUrl = 'wss://ice.plezy.app/relay'; WebSocketChannel? _channel;