diff --git a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt index e16f56a7..e7e3a7e9 100644 --- a/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt +++ b/android/app/src/main/kotlin/com/edde746/plezy/exoplayer/ExoPlayerCore.kt @@ -722,6 +722,22 @@ class ExoPlayerCore(private val activity: Activity) : Player.Listener { // If native DV7 failed, retry with conversion before falling to MPV if (error.errorCode in 4001..4005 && retryWithDvConversion("decoder error ${error.errorCode}")) return + // Server returned HTTP 500 — typically a shared-user bandwidth/transcoding limit + // set by the server owner. MPV will hit the same rejection, so skip the fallback. + // Keep the "server-http-500" tag in sync with PlayerError.serverHttp500 in Dart. + val isHttp500 = + causeChain.contains("Response code: 500") || + (error.message?.contains("Response code: 500") == true) + if (isHttp500) { + Log.w(TAG, "Server returned HTTP 500 - skipping MPV fallback (unrecoverable until server-side change)") + delegate?.onEvent("end-file", mapOf( + "reason" to "error", + "message" to (error.message ?: "HTTP 500"), + "cause" to "server-http-500" + )) + return + } + if (currentMediaUri != null) { Log.w(TAG, "ExoPlayer error (code ${error.errorCode}) - attempting fallback to MPV") val handled = delegate?.onFormatUnsupported( diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index 0b6e591f..14c4b4c2 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Kunne ikke oprette afspilningskø — ingen elementer", "failedPlayback": "Kunne ikke ${action}: ${error}", "switchingToCompatiblePlayer": "Skifter til kompatibel afspiller...", + "serverLimitTitle": "Afspilning mislykkedes", + "serverLimitBody": "Serveren returnerede en fejl (HTTP 500). Det betyder som regel, at serverejeren har sat en båndbredde- eller transkodningsgrænse, der afviser din session. Der er intet at gøre fra klienten — serverejeren skal justere sine indstillinger.", "logsUploaded": "Logs uploadet", "logsUploadFailed": "Kunne ikke uploade logs", "logId": "Log-ID" diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 42f603fe..9ccf81b4 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Wiedergabewarteschlange konnte nicht erstellt werden – keine Elemente", "failedPlayback": "Wiedergabe für ${action} fehlgeschlagen: ${error}", "switchingToCompatiblePlayer": "Wechsle zu kompatiblem Player...", + "serverLimitTitle": "Wiedergabe fehlgeschlagen", + "serverLimitBody": "Der Server hat einen Fehler zurückgegeben (HTTP 500). Das bedeutet meist, dass der Serverbesitzer ein Bandbreiten- oder Transcoding-Limit festgelegt hat, das deine Sitzung ablehnt. Vom Client aus lässt sich nichts machen — der Serverbesitzer muss seine Einstellungen anpassen.", "logsUploaded": "Protokolle hochgeladen", "logsUploadFailed": "Protokolle konnten nicht hochgeladen werden", "logId": "Protokoll-ID" diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index a0edfa03..00b7f8dd 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Failed to create play queue - no items", "failedPlayback": "Failed to ${action}: ${error}", "switchingToCompatiblePlayer": "Switching to compatible player...", + "serverLimitTitle": "Playback failed", + "serverLimitBody": "The server returned an error (HTTP 500). This usually means the server owner has set a bandwidth or transcoding limit that's rejecting your session. There's nothing to do from the client — the server owner needs to adjust their settings.", "logsUploaded": "Logs uploaded", "logsUploadFailed": "Failed to upload logs", "logId": "Log ID" diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 44ed014b..4c6102ef 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Error al crear la cola de reproducción - no hay elementos", "failedPlayback": "Error al ${action}: ${error}", "switchingToCompatiblePlayer": "Cambiando a reproductor compatible...", + "serverLimitTitle": "Error de reproducción", + "serverLimitBody": "El servidor devolvió un error (HTTP 500). Normalmente esto significa que el propietario del servidor ha establecido un límite de ancho de banda o transcodificación que está rechazando tu sesión. No hay nada que puedas hacer desde el cliente — el propietario del servidor debe ajustar su configuración.", "logsUploaded": "Registros subidos", "logsUploadFailed": "Error al subir registros", "logId": "ID de registro" diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 2b0600ef..6fd0e1be 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Échec de la création de la file d'attente de lecture - aucun élément", "failedPlayback": "Echec de ${action}: ${error}", "switchingToCompatiblePlayer": "Passage au lecteur compatible...", + "serverLimitTitle": "Échec de la lecture", + "serverLimitBody": "Le serveur a renvoyé une erreur (HTTP 500). Cela signifie généralement que le propriétaire du serveur a défini une limite de bande passante ou de transcodage qui rejette ta session. Rien à faire côté client — le propriétaire du serveur doit ajuster ses paramètres.", "logsUploaded": "Logs envoyés", "logsUploadFailed": "Échec de l'envoi des logs", "logId": "ID du log" diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 95f90706..aa0e5fa3 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Impossibile creare la coda di riproduzione - nessun elemento", "failedPlayback": "Impossibile ${action}: ${error}", "switchingToCompatiblePlayer": "Passaggio al lettore compatibile...", + "serverLimitTitle": "Riproduzione non riuscita", + "serverLimitBody": "Il server ha restituito un errore (HTTP 500). Di solito significa che il proprietario del server ha impostato un limite di banda o di transcodifica che sta rifiutando la tua sessione. Non c'è nulla da fare dal client — il proprietario del server deve modificare le sue impostazioni.", "logsUploaded": "Log caricati", "logsUploadFailed": "Caricamento log fallito", "logId": "ID log" diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 9edcc641..0e33bac9 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "再生キューの作成に失敗しました - アイテムがありません", "failedPlayback": "${action}に失敗しました: ${error}", "switchingToCompatiblePlayer": "互換プレーヤーに切替中...", + "serverLimitTitle": "再生に失敗しました", + "serverLimitBody": "サーバーからエラーが返されました (HTTP 500)。通常、これはサーバーの所有者が帯域幅またはトランスコードの制限を設定しており、セッションが拒否されていることを意味します。クライアント側でできることはありません — サーバーの所有者が設定を調整する必要があります。", "logsUploaded": "ログをアップロードしました", "logsUploadFailed": "ログのアップロードに失敗しました", "logId": "ログID" diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 925074c8..cbbe8259 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "재생 대기열 생성 실패 - 항목 없음", "failedPlayback": "${action}을(를) 수행할 수 없습니다: ${error}", "switchingToCompatiblePlayer": "호환되는 플레이어로 전환 중...", + "serverLimitTitle": "재생 실패", + "serverLimitBody": "서버에서 오류를 반환했습니다 (HTTP 500). 일반적으로 이는 서버 소유자가 대역폭 또는 트랜스코딩 제한을 설정하여 세션을 거부하고 있다는 의미입니다. 클라이언트에서 할 수 있는 일은 없습니다 — 서버 소유자가 설정을 조정해야 합니다.", "logsUploaded": "로그 업로드 완료", "logsUploadFailed": "로그 업로드 실패", "logId": "로그 ID" diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index 48596fac..1da3ffc7 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Kunne ikke opprette avspillingskø – ingen elementer", "failedPlayback": "Kunne ikke ${action}: ${error}", "switchingToCompatiblePlayer": "Bytter til kompatibel spiller...", + "serverLimitTitle": "Avspilling mislyktes", + "serverLimitBody": "Serveren returnerte en feil (HTTP 500). Det betyr vanligvis at servereieren har satt en båndbredde- eller transkodingsgrense som avviser økten din. Det er ingenting du kan gjøre fra klienten — servereieren må justere innstillingene sine.", "logsUploaded": "Logger lastet opp", "logsUploadFailed": "Kunne ikke laste opp logger", "logId": "Logg-ID" diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 8e56d5a6..3e3f173b 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Kan afspeelwachtrij niet maken - geen items", "failedPlayback": "Afspelen van ${action} mislukt: ${error}", "switchingToCompatiblePlayer": "Overschakelen naar compatibele speler...", + "serverLimitTitle": "Afspelen mislukt", + "serverLimitBody": "De server gaf een fout terug (HTTP 500). Dit betekent meestal dat de servereigenaar een bandbreedte- of transcoderingslimiet heeft ingesteld die jouw sessie weigert. Er valt vanaf de client niets te doen — de servereigenaar moet de instellingen aanpassen.", "logsUploaded": "Logs geüpload", "logsUploadFailed": "Uploaden van logs mislukt", "logId": "Log-ID" diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 562784ff..776cfc4a 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Nie udało się utworzyć kolejki odtwarzania — brak elementów", "failedPlayback": "Nie udało się ${action}: ${error}", "switchingToCompatiblePlayer": "Przełączanie na kompatybilny odtwarzacz...", + "serverLimitTitle": "Odtwarzanie nie powiodło się", + "serverLimitBody": "Serwer zwrócił błąd (HTTP 500). Zwykle oznacza to, że właściciel serwera ustawił limit przepustowości lub transkodowania, który odrzuca Twoją sesję. Z poziomu klienta nie da się nic zrobić — właściciel serwera musi dostosować swoje ustawienia.", "logsUploaded": "Logi przesłane", "logsUploadFailed": "Nie udało się przesłać logów", "logId": "ID logu" diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index 7d444d1b..3d80899a 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Falha ao criar fila de reprodução - sem itens", "failedPlayback": "Falha ao ${action}: ${error}", "switchingToCompatiblePlayer": "Alternando para player compatível...", + "serverLimitTitle": "Falha na reprodução", + "serverLimitBody": "O servidor retornou um erro (HTTP 500). Isso geralmente significa que o proprietário do servidor definiu um limite de largura de banda ou transcodificação que está rejeitando sua sessão. Não há nada a ser feito pelo cliente — o proprietário do servidor precisa ajustar as configurações.", "logsUploaded": "Logs enviados", "logsUploadFailed": "Falha ao enviar logs", "logId": "ID do Log" diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index 0fe0278a..8c02d2fc 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Не удалось создать очередь воспроизведения — нет элементов", "failedPlayback": "Не удалось ${action}: ${error}", "switchingToCompatiblePlayer": "Переключение на совместимый плеер...", + "serverLimitTitle": "Ошибка воспроизведения", + "serverLimitBody": "Сервер вернул ошибку (HTTP 500). Обычно это означает, что владелец сервера установил лимит пропускной способности или транскодирования, из-за которого ваш сеанс отклоняется. Со стороны клиента сделать ничего нельзя — владельцу сервера нужно изменить настройки.", "logsUploaded": "Логи загружены", "logsUploadFailed": "Не удалось загрузить логи", "logId": "ID лога" diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 6a3f1a6f..1123f9f5 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 15 -/// Strings: 13230 (882 per locale) +/// Strings: 13260 (884 per locale) /// -/// Built on 2026-04-16 at 10:46 UTC +/// Built on 2026-04-17 at 10:54 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index b14d6a40..9f9fc1a1 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesDa implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Kunne ikke oprette afspilningskø — ingen elementer'; @override String failedPlayback({required Object action, required Object error}) => 'Kunne ikke ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Skifter til kompatibel afspiller...'; + @override String get serverLimitTitle => 'Afspilning mislykkedes'; + @override String get serverLimitBody => 'Serveren returnerede en fejl (HTTP 500). Det betyder som regel, at serverejeren har sat en båndbredde- eller transkodningsgrænse, der afviser din session. Der er intet at gøre fra klienten — serverejeren skal justere sine indstillinger.'; @override String get logsUploaded => 'Logs uploadet'; @override String get logsUploadFailed => 'Kunne ikke uploade logs'; @override String get logId => 'Log-ID'; @@ -1789,6 +1791,8 @@ extension on TranslationsDa { 'messages.failedToCreatePlayQueueNoItems' => 'Kunne ikke oprette afspilningskø — ingen elementer', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Kunne ikke ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Skifter til kompatibel afspiller...', + 'messages.serverLimitTitle' => 'Afspilning mislykkedes', + 'messages.serverLimitBody' => 'Serveren returnerede en fejl (HTTP 500). Det betyder som regel, at serverejeren har sat en båndbredde- eller transkodningsgrænse, der afviser din session. Der er intet at gøre fra klienten — serverejeren skal justere sine indstillinger.', 'messages.logsUploaded' => 'Logs uploadet', 'messages.logsUploadFailed' => 'Kunne ikke uploade logs', 'messages.logId' => 'Log-ID', @@ -1889,10 +1893,10 @@ extension on TranslationsDa { 'libraries.groupings.movies' => 'Film', 'libraries.groupings.shows' => 'TV-serier', 'libraries.groupings.seasons' => 'Sæsoner', - 'libraries.groupings.episodes' => 'Episoder', - 'libraries.groupings.folders' => 'Mapper', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Episoder', + 'libraries.groupings.folders' => 'Mapper', 'about.title' => 'Om', 'about.openSourceLicenses' => 'Open source-licenser', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 570dfd47..90dbafd6 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesDe implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Wiedergabewarteschlange konnte nicht erstellt werden – keine Elemente'; @override String failedPlayback({required Object action, required Object error}) => 'Wiedergabe für ${action} fehlgeschlagen: ${error}'; @override String get switchingToCompatiblePlayer => 'Wechsle zu kompatiblem Player...'; + @override String get serverLimitTitle => 'Wiedergabe fehlgeschlagen'; + @override String get serverLimitBody => 'Der Server hat einen Fehler zurückgegeben (HTTP 500). Das bedeutet meist, dass der Serverbesitzer ein Bandbreiten- oder Transcoding-Limit festgelegt hat, das deine Sitzung ablehnt. Vom Client aus lässt sich nichts machen — der Serverbesitzer muss seine Einstellungen anpassen.'; @override String get logsUploaded => 'Protokolle hochgeladen'; @override String get logsUploadFailed => 'Protokolle konnten nicht hochgeladen werden'; @override String get logId => 'Protokoll-ID'; @@ -1789,6 +1791,8 @@ extension on TranslationsDe { 'messages.failedToCreatePlayQueueNoItems' => 'Wiedergabewarteschlange konnte nicht erstellt werden – keine Elemente', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Wiedergabe für ${action} fehlgeschlagen: ${error}', 'messages.switchingToCompatiblePlayer' => 'Wechsle zu kompatiblem Player...', + 'messages.serverLimitTitle' => 'Wiedergabe fehlgeschlagen', + 'messages.serverLimitBody' => 'Der Server hat einen Fehler zurückgegeben (HTTP 500). Das bedeutet meist, dass der Serverbesitzer ein Bandbreiten- oder Transcoding-Limit festgelegt hat, das deine Sitzung ablehnt. Vom Client aus lässt sich nichts machen — der Serverbesitzer muss seine Einstellungen anpassen.', 'messages.logsUploaded' => 'Protokolle hochgeladen', 'messages.logsUploadFailed' => 'Protokolle konnten nicht hochgeladen werden', 'messages.logId' => 'Protokoll-ID', @@ -1889,10 +1893,10 @@ extension on TranslationsDe { 'libraries.groupings.movies' => 'Filme', 'libraries.groupings.shows' => 'Serien', 'libraries.groupings.seasons' => 'Staffeln', - 'libraries.groupings.episodes' => 'Episoden', - 'libraries.groupings.folders' => 'Ordner', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Episoden', + 'libraries.groupings.folders' => 'Ordner', 'about.title' => 'Über', 'about.openSourceLicenses' => 'Open-Source-Lizenzen', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 0e8999a1..9df2045b 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -1370,6 +1370,12 @@ class TranslationsMessagesEn { /// en: 'Switching to compatible player...' String get switchingToCompatiblePlayer => 'Switching to compatible player...'; + /// en: 'Playback failed' + String get serverLimitTitle => 'Playback failed'; + + /// en: 'The server returned an error (HTTP 500). This usually means the server owner has set a bandwidth or transcoding limit that's rejecting your session. There's nothing to do from the client — the server owner needs to adjust their settings.' + String get serverLimitBody => 'The server returned an error (HTTP 500). This usually means the server owner has set a bandwidth or transcoding limit that\'s rejecting your session. There\'s nothing to do from the client — the server owner needs to adjust their settings.'; + /// en: 'Logs uploaded' String get logsUploaded => 'Logs uploaded'; @@ -3560,6 +3566,8 @@ extension on Translations { 'messages.failedToCreatePlayQueueNoItems' => 'Failed to create play queue - no items', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Failed to ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Switching to compatible player...', + 'messages.serverLimitTitle' => 'Playback failed', + 'messages.serverLimitBody' => 'The server returned an error (HTTP 500). This usually means the server owner has set a bandwidth or transcoding limit that\'s rejecting your session. There\'s nothing to do from the client — the server owner needs to adjust their settings.', 'messages.logsUploaded' => 'Logs uploaded', 'messages.logsUploadFailed' => 'Failed to upload logs', 'messages.logId' => 'Log ID', @@ -3660,10 +3668,10 @@ extension on Translations { 'libraries.groupings.movies' => 'Movies', 'libraries.groupings.shows' => 'TV Shows', 'libraries.groupings.seasons' => 'Seasons', - 'libraries.groupings.episodes' => 'Episodes', - 'libraries.groupings.folders' => 'Folders', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Episodes', + 'libraries.groupings.folders' => 'Folders', 'about.title' => 'About', 'about.openSourceLicenses' => 'Open Source Licenses', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 1ca0b064..9e2284d0 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesEs implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Error al crear la cola de reproducción - no hay elementos'; @override String failedPlayback({required Object action, required Object error}) => 'Error al ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Cambiando a reproductor compatible...'; + @override String get serverLimitTitle => 'Error de reproducción'; + @override String get serverLimitBody => 'El servidor devolvió un error (HTTP 500). Normalmente esto significa que el propietario del servidor ha establecido un límite de ancho de banda o transcodificación que está rechazando tu sesión. No hay nada que puedas hacer desde el cliente — el propietario del servidor debe ajustar su configuración.'; @override String get logsUploaded => 'Registros subidos'; @override String get logsUploadFailed => 'Error al subir registros'; @override String get logId => 'ID de registro'; @@ -1789,6 +1791,8 @@ extension on TranslationsEs { 'messages.failedToCreatePlayQueueNoItems' => 'Error al crear la cola de reproducción - no hay elementos', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Error al ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Cambiando a reproductor compatible...', + 'messages.serverLimitTitle' => 'Error de reproducción', + 'messages.serverLimitBody' => 'El servidor devolvió un error (HTTP 500). Normalmente esto significa que el propietario del servidor ha establecido un límite de ancho de banda o transcodificación que está rechazando tu sesión. No hay nada que puedas hacer desde el cliente — el propietario del servidor debe ajustar su configuración.', 'messages.logsUploaded' => 'Registros subidos', 'messages.logsUploadFailed' => 'Error al subir registros', 'messages.logId' => 'ID de registro', @@ -1889,10 +1893,10 @@ extension on TranslationsEs { 'libraries.groupings.movies' => 'Películas', 'libraries.groupings.shows' => 'Series', 'libraries.groupings.seasons' => 'Temporadas', - 'libraries.groupings.episodes' => 'Episodios', - 'libraries.groupings.folders' => 'Carpetas', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Episodios', + 'libraries.groupings.folders' => 'Carpetas', 'about.title' => 'Acerca de', 'about.openSourceLicenses' => 'Licencias de Código Abierto', 'about.versionLabel' => ({required Object version}) => 'Versión ${version}', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 80b8de89..b96a2ad1 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesFr implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Échec de la création de la file d\'attente de lecture - aucun élément'; @override String failedPlayback({required Object action, required Object error}) => 'Echec de ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Passage au lecteur compatible...'; + @override String get serverLimitTitle => 'Échec de la lecture'; + @override String get serverLimitBody => 'Le serveur a renvoyé une erreur (HTTP 500). Cela signifie généralement que le propriétaire du serveur a défini une limite de bande passante ou de transcodage qui rejette ta session. Rien à faire côté client — le propriétaire du serveur doit ajuster ses paramètres.'; @override String get logsUploaded => 'Logs envoyés'; @override String get logsUploadFailed => 'Échec de l\'envoi des logs'; @override String get logId => 'ID du log'; @@ -1789,6 +1791,8 @@ extension on TranslationsFr { 'messages.failedToCreatePlayQueueNoItems' => 'Échec de la création de la file d\'attente de lecture - aucun élément', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Echec de ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Passage au lecteur compatible...', + 'messages.serverLimitTitle' => 'Échec de la lecture', + 'messages.serverLimitBody' => 'Le serveur a renvoyé une erreur (HTTP 500). Cela signifie généralement que le propriétaire du serveur a défini une limite de bande passante ou de transcodage qui rejette ta session. Rien à faire côté client — le propriétaire du serveur doit ajuster ses paramètres.', 'messages.logsUploaded' => 'Logs envoyés', 'messages.logsUploadFailed' => 'Échec de l\'envoi des logs', 'messages.logId' => 'ID du log', @@ -1889,10 +1893,10 @@ extension on TranslationsFr { 'libraries.groupings.movies' => 'Films', 'libraries.groupings.shows' => 'Show TV', 'libraries.groupings.seasons' => 'Saisons', - 'libraries.groupings.episodes' => 'Épisodes', - 'libraries.groupings.folders' => 'Dossiers', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Épisodes', + 'libraries.groupings.folders' => 'Dossiers', 'about.title' => 'À propos', 'about.openSourceLicenses' => 'Licences Open Source', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index 1b29b5f1..3d0982ca 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesIt implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Impossibile creare la coda di riproduzione - nessun elemento'; @override String failedPlayback({required Object action, required Object error}) => 'Impossibile ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Passaggio al lettore compatibile...'; + @override String get serverLimitTitle => 'Riproduzione non riuscita'; + @override String get serverLimitBody => 'Il server ha restituito un errore (HTTP 500). Di solito significa che il proprietario del server ha impostato un limite di banda o di transcodifica che sta rifiutando la tua sessione. Non c\'è nulla da fare dal client — il proprietario del server deve modificare le sue impostazioni.'; @override String get logsUploaded => 'Log caricati'; @override String get logsUploadFailed => 'Caricamento log fallito'; @override String get logId => 'ID log'; @@ -1789,6 +1791,8 @@ extension on TranslationsIt { 'messages.failedToCreatePlayQueueNoItems' => 'Impossibile creare la coda di riproduzione - nessun elemento', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Impossibile ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Passaggio al lettore compatibile...', + 'messages.serverLimitTitle' => 'Riproduzione non riuscita', + 'messages.serverLimitBody' => 'Il server ha restituito un errore (HTTP 500). Di solito significa che il proprietario del server ha impostato un limite di banda o di transcodifica che sta rifiutando la tua sessione. Non c\'è nulla da fare dal client — il proprietario del server deve modificare le sue impostazioni.', 'messages.logsUploaded' => 'Log caricati', 'messages.logsUploadFailed' => 'Caricamento log fallito', 'messages.logId' => 'ID log', @@ -1889,10 +1893,10 @@ extension on TranslationsIt { 'libraries.groupings.movies' => 'Film', 'libraries.groupings.shows' => 'Serie TV', 'libraries.groupings.seasons' => 'Stagioni', - 'libraries.groupings.episodes' => 'Episodi', - 'libraries.groupings.folders' => 'Cartelle', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Episodi', + 'libraries.groupings.folders' => 'Cartelle', 'about.title' => 'Informazioni', 'about.openSourceLicenses' => 'Licenze Open Source', 'about.versionLabel' => ({required Object version}) => 'Versione ${version}', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index 25ce5f84..9e2e0eac 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesJa implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => '再生キューの作成に失敗しました - アイテムがありません'; @override String failedPlayback({required Object action, required Object error}) => '${action}に失敗しました: ${error}'; @override String get switchingToCompatiblePlayer => '互換プレーヤーに切替中...'; + @override String get serverLimitTitle => '再生に失敗しました'; + @override String get serverLimitBody => 'サーバーからエラーが返されました (HTTP 500)。通常、これはサーバーの所有者が帯域幅またはトランスコードの制限を設定しており、セッションが拒否されていることを意味します。クライアント側でできることはありません — サーバーの所有者が設定を調整する必要があります。'; @override String get logsUploaded => 'ログをアップロードしました'; @override String get logsUploadFailed => 'ログのアップロードに失敗しました'; @override String get logId => 'ログID'; @@ -1789,6 +1791,8 @@ extension on TranslationsJa { 'messages.failedToCreatePlayQueueNoItems' => '再生キューの作成に失敗しました - アイテムがありません', 'messages.failedPlayback' => ({required Object action, required Object error}) => '${action}に失敗しました: ${error}', 'messages.switchingToCompatiblePlayer' => '互換プレーヤーに切替中...', + 'messages.serverLimitTitle' => '再生に失敗しました', + 'messages.serverLimitBody' => 'サーバーからエラーが返されました (HTTP 500)。通常、これはサーバーの所有者が帯域幅またはトランスコードの制限を設定しており、セッションが拒否されていることを意味します。クライアント側でできることはありません — サーバーの所有者が設定を調整する必要があります。', 'messages.logsUploaded' => 'ログをアップロードしました', 'messages.logsUploadFailed' => 'ログのアップロードに失敗しました', 'messages.logId' => 'ログID', @@ -1889,10 +1893,10 @@ extension on TranslationsJa { 'libraries.groupings.movies' => '映画', 'libraries.groupings.shows' => 'テレビ番組', 'libraries.groupings.seasons' => 'シーズン', - 'libraries.groupings.episodes' => 'エピソード', - 'libraries.groupings.folders' => 'フォルダ', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'エピソード', + 'libraries.groupings.folders' => 'フォルダ', 'about.title' => 'アプリについて', 'about.openSourceLicenses' => 'オープンソースライセンス', 'about.versionLabel' => ({required Object version}) => 'バージョン ${version}', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 0330dcad..4722d0a6 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesKo implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => '재생 대기열 생성 실패 - 항목 없음'; @override String failedPlayback({required Object action, required Object error}) => '${action}을(를) 수행할 수 없습니다: ${error}'; @override String get switchingToCompatiblePlayer => '호환되는 플레이어로 전환 중...'; + @override String get serverLimitTitle => '재생 실패'; + @override String get serverLimitBody => '서버에서 오류를 반환했습니다 (HTTP 500). 일반적으로 이는 서버 소유자가 대역폭 또는 트랜스코딩 제한을 설정하여 세션을 거부하고 있다는 의미입니다. 클라이언트에서 할 수 있는 일은 없습니다 — 서버 소유자가 설정을 조정해야 합니다.'; @override String get logsUploaded => '로그 업로드 완료'; @override String get logsUploadFailed => '로그 업로드 실패'; @override String get logId => '로그 ID'; @@ -1789,6 +1791,8 @@ extension on TranslationsKo { 'messages.failedToCreatePlayQueueNoItems' => '재생 대기열 생성 실패 - 항목 없음', 'messages.failedPlayback' => ({required Object action, required Object error}) => '${action}을(를) 수행할 수 없습니다: ${error}', 'messages.switchingToCompatiblePlayer' => '호환되는 플레이어로 전환 중...', + 'messages.serverLimitTitle' => '재생 실패', + 'messages.serverLimitBody' => '서버에서 오류를 반환했습니다 (HTTP 500). 일반적으로 이는 서버 소유자가 대역폭 또는 트랜스코딩 제한을 설정하여 세션을 거부하고 있다는 의미입니다. 클라이언트에서 할 수 있는 일은 없습니다 — 서버 소유자가 설정을 조정해야 합니다.', 'messages.logsUploaded' => '로그 업로드 완료', 'messages.logsUploadFailed' => '로그 업로드 실패', 'messages.logId' => '로그 ID', @@ -1889,10 +1893,10 @@ extension on TranslationsKo { 'libraries.groupings.movies' => '영화', 'libraries.groupings.shows' => 'TV 프로그램', 'libraries.groupings.seasons' => '시즌', - 'libraries.groupings.episodes' => '화', - 'libraries.groupings.folders' => '폴더', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => '화', + 'libraries.groupings.folders' => '폴더', 'about.title' => '소개', 'about.openSourceLicenses' => '오픈소스 라이선스', 'about.versionLabel' => ({required Object version}) => '버전 ${version}', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index 28f8725a..e10b4e86 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesNb implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Kunne ikke opprette avspillingskø – ingen elementer'; @override String failedPlayback({required Object action, required Object error}) => 'Kunne ikke ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Bytter til kompatibel spiller...'; + @override String get serverLimitTitle => 'Avspilling mislyktes'; + @override String get serverLimitBody => 'Serveren returnerte en feil (HTTP 500). Det betyr vanligvis at servereieren har satt en båndbredde- eller transkodingsgrense som avviser økten din. Det er ingenting du kan gjøre fra klienten — servereieren må justere innstillingene sine.'; @override String get logsUploaded => 'Logger lastet opp'; @override String get logsUploadFailed => 'Kunne ikke laste opp logger'; @override String get logId => 'Logg-ID'; @@ -1789,6 +1791,8 @@ extension on TranslationsNb { 'messages.failedToCreatePlayQueueNoItems' => 'Kunne ikke opprette avspillingskø – ingen elementer', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Kunne ikke ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Bytter til kompatibel spiller...', + 'messages.serverLimitTitle' => 'Avspilling mislyktes', + 'messages.serverLimitBody' => 'Serveren returnerte en feil (HTTP 500). Det betyr vanligvis at servereieren har satt en båndbredde- eller transkodingsgrense som avviser økten din. Det er ingenting du kan gjøre fra klienten — servereieren må justere innstillingene sine.', 'messages.logsUploaded' => 'Logger lastet opp', 'messages.logsUploadFailed' => 'Kunne ikke laste opp logger', 'messages.logId' => 'Logg-ID', @@ -1889,10 +1893,10 @@ extension on TranslationsNb { 'libraries.groupings.movies' => 'Filmer', 'libraries.groupings.shows' => 'TV-serier', 'libraries.groupings.seasons' => 'Sesonger', - 'libraries.groupings.episodes' => 'Episoder', - 'libraries.groupings.folders' => 'Mapper', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Episoder', + 'libraries.groupings.folders' => 'Mapper', 'about.title' => 'Om', 'about.openSourceLicenses' => 'Åpen kildekode-lisenser', 'about.versionLabel' => ({required Object version}) => 'Versjon ${version}', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index fa4ce23a..00bd27f8 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesNl implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Kan afspeelwachtrij niet maken - geen items'; @override String failedPlayback({required Object action, required Object error}) => 'Afspelen van ${action} mislukt: ${error}'; @override String get switchingToCompatiblePlayer => 'Overschakelen naar compatibele speler...'; + @override String get serverLimitTitle => 'Afspelen mislukt'; + @override String get serverLimitBody => 'De server gaf een fout terug (HTTP 500). Dit betekent meestal dat de servereigenaar een bandbreedte- of transcoderingslimiet heeft ingesteld die jouw sessie weigert. Er valt vanaf de client niets te doen — de servereigenaar moet de instellingen aanpassen.'; @override String get logsUploaded => 'Logs geüpload'; @override String get logsUploadFailed => 'Uploaden van logs mislukt'; @override String get logId => 'Log-ID'; @@ -1789,6 +1791,8 @@ extension on TranslationsNl { 'messages.failedToCreatePlayQueueNoItems' => 'Kan afspeelwachtrij niet maken - geen items', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Afspelen van ${action} mislukt: ${error}', 'messages.switchingToCompatiblePlayer' => 'Overschakelen naar compatibele speler...', + 'messages.serverLimitTitle' => 'Afspelen mislukt', + 'messages.serverLimitBody' => 'De server gaf een fout terug (HTTP 500). Dit betekent meestal dat de servereigenaar een bandbreedte- of transcoderingslimiet heeft ingesteld die jouw sessie weigert. Er valt vanaf de client niets te doen — de servereigenaar moet de instellingen aanpassen.', 'messages.logsUploaded' => 'Logs geüpload', 'messages.logsUploadFailed' => 'Uploaden van logs mislukt', 'messages.logId' => 'Log-ID', @@ -1889,10 +1893,10 @@ extension on TranslationsNl { 'libraries.groupings.movies' => 'Films', 'libraries.groupings.shows' => 'Series', 'libraries.groupings.seasons' => 'Seizoenen', - 'libraries.groupings.episodes' => 'Afleveringen', - 'libraries.groupings.folders' => 'Mappen', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Afleveringen', + 'libraries.groupings.folders' => 'Mappen', 'about.title' => 'Over', 'about.openSourceLicenses' => 'Open Source licenties', 'about.versionLabel' => ({required Object version}) => 'Versie ${version}', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index 52b914de..7f11d4af 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesPl implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Nie udało się utworzyć kolejki odtwarzania — brak elementów'; @override String failedPlayback({required Object action, required Object error}) => 'Nie udało się ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Przełączanie na kompatybilny odtwarzacz...'; + @override String get serverLimitTitle => 'Odtwarzanie nie powiodło się'; + @override String get serverLimitBody => 'Serwer zwrócił błąd (HTTP 500). Zwykle oznacza to, że właściciel serwera ustawił limit przepustowości lub transkodowania, który odrzuca Twoją sesję. Z poziomu klienta nie da się nic zrobić — właściciel serwera musi dostosować swoje ustawienia.'; @override String get logsUploaded => 'Logi przesłane'; @override String get logsUploadFailed => 'Nie udało się przesłać logów'; @override String get logId => 'ID logu'; @@ -1789,6 +1791,8 @@ extension on TranslationsPl { 'messages.failedToCreatePlayQueueNoItems' => 'Nie udało się utworzyć kolejki odtwarzania — brak elementów', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Nie udało się ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Przełączanie na kompatybilny odtwarzacz...', + 'messages.serverLimitTitle' => 'Odtwarzanie nie powiodło się', + 'messages.serverLimitBody' => 'Serwer zwrócił błąd (HTTP 500). Zwykle oznacza to, że właściciel serwera ustawił limit przepustowości lub transkodowania, który odrzuca Twoją sesję. Z poziomu klienta nie da się nic zrobić — właściciel serwera musi dostosować swoje ustawienia.', 'messages.logsUploaded' => 'Logi przesłane', 'messages.logsUploadFailed' => 'Nie udało się przesłać logów', 'messages.logId' => 'ID logu', @@ -1889,10 +1893,10 @@ extension on TranslationsPl { 'libraries.groupings.movies' => 'Filmy', 'libraries.groupings.shows' => 'Seriale TV', 'libraries.groupings.seasons' => 'Sezony', - 'libraries.groupings.episodes' => 'Odcinki', - 'libraries.groupings.folders' => 'Foldery', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Odcinki', + 'libraries.groupings.folders' => 'Foldery', 'about.title' => 'O aplikacji', 'about.openSourceLicenses' => 'Licencje open source', 'about.versionLabel' => ({required Object version}) => 'Wersja ${version}', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index 813085c6..4374b73e 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesPt implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Falha ao criar fila de reprodução - sem itens'; @override String failedPlayback({required Object action, required Object error}) => 'Falha ao ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Alternando para player compatível...'; + @override String get serverLimitTitle => 'Falha na reprodução'; + @override String get serverLimitBody => 'O servidor retornou um erro (HTTP 500). Isso geralmente significa que o proprietário do servidor definiu um limite de largura de banda ou transcodificação que está rejeitando sua sessão. Não há nada a ser feito pelo cliente — o proprietário do servidor precisa ajustar as configurações.'; @override String get logsUploaded => 'Logs enviados'; @override String get logsUploadFailed => 'Falha ao enviar logs'; @override String get logId => 'ID do Log'; @@ -1789,6 +1791,8 @@ extension on TranslationsPt { 'messages.failedToCreatePlayQueueNoItems' => 'Falha ao criar fila de reprodução - sem itens', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Falha ao ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Alternando para player compatível...', + 'messages.serverLimitTitle' => 'Falha na reprodução', + 'messages.serverLimitBody' => 'O servidor retornou um erro (HTTP 500). Isso geralmente significa que o proprietário do servidor definiu um limite de largura de banda ou transcodificação que está rejeitando sua sessão. Não há nada a ser feito pelo cliente — o proprietário do servidor precisa ajustar as configurações.', 'messages.logsUploaded' => 'Logs enviados', 'messages.logsUploadFailed' => 'Falha ao enviar logs', 'messages.logId' => 'ID do Log', @@ -1889,10 +1893,10 @@ extension on TranslationsPt { 'libraries.groupings.movies' => 'Filmes', 'libraries.groupings.shows' => 'Séries de TV', 'libraries.groupings.seasons' => 'Temporadas', - 'libraries.groupings.episodes' => 'Episódios', - 'libraries.groupings.folders' => 'Pastas', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Episódios', + 'libraries.groupings.folders' => 'Pastas', 'about.title' => 'Sobre', 'about.openSourceLicenses' => 'Licenças Open Source', 'about.versionLabel' => ({required Object version}) => 'Versão ${version}', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index 4ce68799..0c6a1507 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesRu implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Не удалось создать очередь воспроизведения — нет элементов'; @override String failedPlayback({required Object action, required Object error}) => 'Не удалось ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Переключение на совместимый плеер...'; + @override String get serverLimitTitle => 'Ошибка воспроизведения'; + @override String get serverLimitBody => 'Сервер вернул ошибку (HTTP 500). Обычно это означает, что владелец сервера установил лимит пропускной способности или транскодирования, из-за которого ваш сеанс отклоняется. Со стороны клиента сделать ничего нельзя — владельцу сервера нужно изменить настройки.'; @override String get logsUploaded => 'Логи загружены'; @override String get logsUploadFailed => 'Не удалось загрузить логи'; @override String get logId => 'ID лога'; @@ -1789,6 +1791,8 @@ extension on TranslationsRu { 'messages.failedToCreatePlayQueueNoItems' => 'Не удалось создать очередь воспроизведения — нет элементов', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Не удалось ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Переключение на совместимый плеер...', + 'messages.serverLimitTitle' => 'Ошибка воспроизведения', + 'messages.serverLimitBody' => 'Сервер вернул ошибку (HTTP 500). Обычно это означает, что владелец сервера установил лимит пропускной способности или транскодирования, из-за которого ваш сеанс отклоняется. Со стороны клиента сделать ничего нельзя — владельцу сервера нужно изменить настройки.', 'messages.logsUploaded' => 'Логи загружены', 'messages.logsUploadFailed' => 'Не удалось загрузить логи', 'messages.logId' => 'ID лога', @@ -1889,10 +1893,10 @@ extension on TranslationsRu { 'libraries.groupings.movies' => 'Фильмы', 'libraries.groupings.shows' => 'Сериалы', 'libraries.groupings.seasons' => 'Сезоны', - 'libraries.groupings.episodes' => 'Эпизоды', - 'libraries.groupings.folders' => 'Папки', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Эпизоды', + 'libraries.groupings.folders' => 'Папки', 'about.title' => 'О приложении', 'about.openSourceLicenses' => 'Лицензии открытого ПО', 'about.versionLabel' => ({required Object version}) => 'Версия ${version}', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 8b84e8eb..2d5de287 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesSv implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => 'Det gick inte att skapa uppspelningskö – inga objekt'; @override String failedPlayback({required Object action, required Object error}) => 'Kunde inte ${action}: ${error}'; @override String get switchingToCompatiblePlayer => 'Byter till kompatibel spelare...'; + @override String get serverLimitTitle => 'Uppspelningen misslyckades'; + @override String get serverLimitBody => 'Servern returnerade ett fel (HTTP 500). Det betyder oftast att serverägaren har satt en gräns för bandbredd eller transkodning som avvisar din session. Det finns inget du kan göra från klienten — serverägaren behöver justera sina inställningar.'; @override String get logsUploaded => 'Loggar uppladdade'; @override String get logsUploadFailed => 'Uppladdning av loggar misslyckades'; @override String get logId => 'Logg-ID'; @@ -1789,6 +1791,8 @@ extension on TranslationsSv { 'messages.failedToCreatePlayQueueNoItems' => 'Det gick inte att skapa uppspelningskö – inga objekt', 'messages.failedPlayback' => ({required Object action, required Object error}) => 'Kunde inte ${action}: ${error}', 'messages.switchingToCompatiblePlayer' => 'Byter till kompatibel spelare...', + 'messages.serverLimitTitle' => 'Uppspelningen misslyckades', + 'messages.serverLimitBody' => 'Servern returnerade ett fel (HTTP 500). Det betyder oftast att serverägaren har satt en gräns för bandbredd eller transkodning som avvisar din session. Det finns inget du kan göra från klienten — serverägaren behöver justera sina inställningar.', 'messages.logsUploaded' => 'Loggar uppladdade', 'messages.logsUploadFailed' => 'Uppladdning av loggar misslyckades', 'messages.logId' => 'Logg-ID', @@ -1889,10 +1893,10 @@ extension on TranslationsSv { 'libraries.groupings.movies' => 'Filmer', 'libraries.groupings.shows' => 'Serier', 'libraries.groupings.seasons' => 'Säsonger', - 'libraries.groupings.episodes' => 'Avsnitt', - 'libraries.groupings.folders' => 'Mappar', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => 'Avsnitt', + 'libraries.groupings.folders' => 'Mappar', 'about.title' => 'Om', 'about.openSourceLicenses' => 'Öppen källkod-licenser', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index 2a1bb29e..2a7ffe04 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -596,6 +596,8 @@ class _TranslationsMessagesZh implements TranslationsMessagesEn { @override String get failedToCreatePlayQueueNoItems => '创建播放队列失败 - 没有项目'; @override String failedPlayback({required Object action, required Object error}) => '无法${action}: ${error}'; @override String get switchingToCompatiblePlayer => '正在切换到兼容的播放器...'; + @override String get serverLimitTitle => '播放失败'; + @override String get serverLimitBody => '服务器返回了错误 (HTTP 500)。这通常意味着服务器所有者设置了带宽或转码限制,正在拒绝你的会话。客户端无法解决此问题 — 需要服务器所有者调整其设置。'; @override String get logsUploaded => '日志已上传'; @override String get logsUploadFailed => '上传日志失败'; @override String get logId => '日志 ID'; @@ -1789,6 +1791,8 @@ extension on TranslationsZh { 'messages.failedToCreatePlayQueueNoItems' => '创建播放队列失败 - 没有项目', 'messages.failedPlayback' => ({required Object action, required Object error}) => '无法${action}: ${error}', 'messages.switchingToCompatiblePlayer' => '正在切换到兼容的播放器...', + 'messages.serverLimitTitle' => '播放失败', + 'messages.serverLimitBody' => '服务器返回了错误 (HTTP 500)。这通常意味着服务器所有者设置了带宽或转码限制,正在拒绝你的会话。客户端无法解决此问题 — 需要服务器所有者调整其设置。', 'messages.logsUploaded' => '日志已上传', 'messages.logsUploadFailed' => '上传日志失败', 'messages.logId' => '日志 ID', @@ -1889,10 +1893,10 @@ extension on TranslationsZh { 'libraries.groupings.movies' => '电影', 'libraries.groupings.shows' => '剧集', 'libraries.groupings.seasons' => '季', - 'libraries.groupings.episodes' => '集', - 'libraries.groupings.folders' => '文件夹', _ => null, } ?? switch (path) { + 'libraries.groupings.episodes' => '集', + 'libraries.groupings.folders' => '文件夹', 'about.title' => '关于', 'about.openSourceLicenses' => '开源许可证', 'about.versionLabel' => ({required Object version}) => '版本 ${version}', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 606a805e..aaa3dbc2 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "Det gick inte att skapa uppspelningskö – inga objekt", "failedPlayback": "Kunde inte ${action}: ${error}", "switchingToCompatiblePlayer": "Byter till kompatibel spelare...", + "serverLimitTitle": "Uppspelningen misslyckades", + "serverLimitBody": "Servern returnerade ett fel (HTTP 500). Det betyder oftast att serverägaren har satt en gräns för bandbredd eller transkodning som avvisar din session. Det finns inget du kan göra från klienten — serverägaren behöver justera sina inställningar.", "logsUploaded": "Loggar uppladdade", "logsUploadFailed": "Uppladdning av loggar misslyckades", "logId": "Logg-ID" diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 53a342a3..59a29336 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -442,6 +442,8 @@ "failedToCreatePlayQueueNoItems": "创建播放队列失败 - 没有项目", "failedPlayback": "无法${action}: ${error}", "switchingToCompatiblePlayer": "正在切换到兼容的播放器...", + "serverLimitTitle": "播放失败", + "serverLimitBody": "服务器返回了错误 (HTTP 500)。这通常意味着服务器所有者设置了带宽或转码限制,正在拒绝你的会话。客户端无法解决此问题 — 需要服务器所有者调整其设置。", "logsUploaded": "日志已上传", "logsUploadFailed": "上传日志失败", "logId": "日志 ID" diff --git a/lib/mpv/models.dart b/lib/mpv/models.dart index 0324d46e..c6196d9b 100644 --- a/lib/mpv/models.dart +++ b/lib/mpv/models.dart @@ -5,6 +5,24 @@ class BufferRange { const BufferRange({required this.start, required this.end}); } +/// A playback error emitted by the player. +/// +/// [cause] is an optional machine-readable tag (e.g. `server-http-500`) set by +/// the native layer when it can classify the failure, letting the UI branch +/// without parsing [message]. +class PlayerError { + /// Cause tag for a server-side HTTP 500 — shared-user bandwidth or + /// transcoding limit rejection set by the server owner. + static const String serverHttp500 = 'server-http-500'; + + final String message; + final String? cause; + const PlayerError(this.message, {this.cause}); + + @override + String toString() => message; +} + /// Log level for player messages. enum PlayerLogLevel { /// No logging. diff --git a/lib/mpv/player/platform/player_android.dart b/lib/mpv/player/platform/player_android.dart index 6dd7bfb4..3d361359 100644 --- a/lib/mpv/player/platform/player_android.dart +++ b/lib/mpv/player/platform/player_android.dart @@ -82,7 +82,7 @@ class PlayerAndroid extends PlayerBase { await observeProperty('sid', 'string'); await observeProperty('demuxer-cache-time', 'double'); } catch (e) { - errorController.add('Initialization failed: $e'); + errorController.add(PlayerError('Initialization failed: $e')); rethrow; } } diff --git a/lib/mpv/player/player_base.dart b/lib/mpv/player/player_base.dart index 69a3a3f2..c6b95490 100644 --- a/lib/mpv/player/player_base.dart +++ b/lib/mpv/player/player_base.dart @@ -94,7 +94,7 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player { _handleEvent, onError: (error) { if (_disposed) return; - errorController.add(error.toString()); + errorController.add(PlayerError(error.toString())); }, ); } @@ -356,7 +356,10 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player { _state = _state.copyWith(completed: true); completedController.add(true); } else if (reason == 'error') { - errorController.add(data?['message'] as String? ?? 'Playback error'); + errorController.add(PlayerError( + data?['message'] as String? ?? 'Playback error', + cause: data?['cause'] as String?, + )); } break; @@ -528,7 +531,7 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player { await invoke('setVisible', {'visible': visible}); return true; } catch (e) { - errorController.add('Failed to set visibility: $e'); + errorController.add(PlayerError('Failed to set visibility: $e')); return false; } } @@ -596,6 +599,27 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player { } } + // ============================================ + // Debug helpers + // ============================================ + + /// Injects the log + error events that would fire when the server rejects the + /// stream with HTTP 500 (shared-user bandwidth / transcoding limit). Used by + /// the in-player debug button to preview the end-to-end detection path + /// without needing a real misbehaving server. + void debugSimulateServer500() { + if (_disposed) return; + logController.add(const PlayerLog( + level: PlayerLogLevel.warn, + prefix: 'ffmpeg', + text: 'https: HTTP error 500 Internal Server Error', + )); + errorController.add(const PlayerError( + 'HTTP 500', + cause: PlayerError.serverHttp500, + )); + } + // ============================================ // Lifecycle // ============================================ diff --git a/lib/mpv/player/player_native.dart b/lib/mpv/player/player_native.dart index 96fb3506..25fe688e 100644 --- a/lib/mpv/player/player_native.dart +++ b/lib/mpv/player/player_native.dart @@ -71,7 +71,7 @@ class PlayerNative extends PlayerBase { await observeProperty('audio-device-list', _nodeFormat); await observeProperty('audio-device', 'string'); } catch (e) { - errorController.add('Initialization failed: $e'); + errorController.add(PlayerError('Initialization failed: $e')); rethrow; } } diff --git a/lib/mpv/player/player_stream_controllers.dart b/lib/mpv/player/player_stream_controllers.dart index 978282c4..1d775cae 100644 --- a/lib/mpv/player/player_stream_controllers.dart +++ b/lib/mpv/player/player_stream_controllers.dart @@ -21,7 +21,7 @@ mixin PlayerStreamControllersMixin { final tracksController = StreamController.broadcast(); final trackController = StreamController.broadcast(); final logController = StreamController.broadcast(); - final errorController = StreamController.broadcast(); + final errorController = StreamController.broadcast(); final audioDeviceController = StreamController.broadcast(); final audioDevicesController = StreamController>.broadcast(); final bufferRangesController = StreamController>.broadcast(); diff --git a/lib/mpv/player/player_streams.dart b/lib/mpv/player/player_streams.dart index 0efb0043..2f1bb6a5 100644 --- a/lib/mpv/player/player_streams.dart +++ b/lib/mpv/player/player_streams.dart @@ -41,8 +41,8 @@ class PlayerStreams { /// Stream of log messages from the player. final Stream log; - /// Stream of error messages. - final Stream error; + /// Stream of player errors. + final Stream error; /// Stream of audio device changes. final Stream audioDevice; diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index ef928ab1..b7731b1b 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -154,7 +154,7 @@ class VideoPlayerScreenState extends State with WidgetsBindin bool _isPhone = false; List _availableVersions = []; PlexMediaInfo? _currentMediaInfo; - StreamSubscription? _errorSubscription; + StreamSubscription? _errorSubscription; StreamSubscription? _playingSubscription; StreamSubscription? _completedSubscription; StreamSubscription? _mediaControlSubscription; @@ -740,10 +740,15 @@ class VideoPlayerScreenState extends State with WidgetsBindin // Listen to MPV errors _errorSubscription = player!.streams.error.listen(_onPlayerError); - // Listen to error-level log messages for user-visible snackbars + // warn is included so we can catch ffmpeg's "HTTP error 500" line in + // _onPlayerLog — the error-level log that follows omits the status code. _logSubscription = player!.streams.log - .where((log) => log.level == PlayerLogLevel.error || log.level == PlayerLogLevel.fatal) - .listen(_onPlayerLogError); + .where((log) => const { + PlayerLogLevel.fatal, + PlayerLogLevel.error, + PlayerLogLevel.warn, + }.contains(log.level)) + .listen(_onPlayerLog); // Listen for backend switched event (ExoPlayer -> MPV fallback on Android) if (Platform.isAndroid && useExoPlayer) { @@ -778,6 +783,7 @@ class VideoPlayerScreenState extends State with WidgetsBindin // Listen to playback restart to detect first frame ready _playbackRestartSubscription = player!.streams.playbackRestart.listen((_) async { _lastLogError = null; + _sawServer500 = false; _liveStreamFallbackLevel = 0; if (!_hasFirstFrame.value) { _hasFirstFrame.value = true; @@ -2241,10 +2247,16 @@ class VideoPlayerScreenState extends State with WidgetsBindin } } - void _onPlayerError(String error) { - appLogger.e('[Player ERROR] $error'); + void _onPlayerError(PlayerError err) { + appLogger.e('[Player ERROR] ${err.message}'); if (!mounted || _isExiting.value) return; + // Fatal, unrecoverable until server-side fix — show modal instead of a snackbar. + if (err.cause == PlayerError.serverHttp500 || _sawServer500) { + _showServerLimitDialog(); + return; + } + // Live TV: retry with progressively degraded stream settings // (mirrors Plex web client fallback chain). if (widget.isLive && _liveStreamFallbackLevel < 2 && !_isRetryingLiveStream) { @@ -2255,15 +2267,30 @@ class VideoPlayerScreenState extends State with WidgetsBindin return; } - showGlobalErrorSnackBar(_lastLogError ?? error); + showGlobalErrorSnackBar(_lastLogError ?? err.message); _handleBackButton(); } String? _lastLogError; + bool _sawServer500 = false; - void _onPlayerLogError(PlayerLog log) { - appLogger.e('[Player LOG ERROR] [${log.prefix}] ${log.text}'); - _lastLogError = log.text.trim(); + static final RegExp _server500Pattern = + RegExp(r'\b(?:HTTP error |Response code: )500\b'); + + void _onPlayerLog(PlayerLog log) { + if (!_sawServer500 && _server500Pattern.hasMatch(log.text)) { + _sawServer500 = true; + } + if (log.level == PlayerLogLevel.error || log.level == PlayerLogLevel.fatal) { + appLogger.e('[Player LOG ERROR] [${log.prefix}] ${log.text}'); + _lastLogError = log.text.trim(); + } + } + + Future _showServerLimitDialog() async { + if (!mounted) return; + await showServerLimitDialog(context); + if (mounted) _handleBackButton(); } /// Handle notification when native player switched from ExoPlayer to MPV diff --git a/lib/utils/dialogs.dart b/lib/utils/dialogs.dart index 8bd29ecc..ac556448 100644 --- a/lib/utils/dialogs.dart +++ b/lib/utils/dialogs.dart @@ -57,6 +57,29 @@ Future showConfirmDialog( return confirmed ?? false; } +/// Shows the server-side 500 modal (bandwidth/transcoding limit rejection). +Future showServerLimitDialog(BuildContext context) async { + await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog( + title: Text(t.messages.serverLimitTitle), + content: Text(t.messages.serverLimitBody), + actions: [ + FocusableButton( + autofocus: true, + onPressed: () => Navigator.of(ctx).pop(), + child: FilledButton( + onPressed: () => Navigator.of(ctx).pop(), + style: FilledButton.styleFrom(padding: _buttonPadding, shape: _buttonShape), + child: Text(t.common.close), + ), + ), + ], + ), + ); +} + /// Shows a confirmation dialog with an optional checkbox (e.g. "Don't ask again"). /// Returns a record with [confirmed] and [checked] booleans. Future<({bool confirmed, bool checked})> showConfirmDialogWithCheckbox( diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index 676670ce..ba7e96ba 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -522,6 +522,19 @@ class _VideoSettingsSheetState extends State { OverlaySheetController.of(context).close(); }, ), + + if (kDebugMode) + FocusableListTile( + leading: AppIcon(Symbols.bug_report_rounded, fill: 1, color: tokens(context).textMuted), + title: const Text('Simulate HTTP 500 from server'), + onTap: () { + final player = widget.player; + OverlaySheetController.of(context).close(); + if (player is PlayerBase) { + player.debugSimulateServer500(); + } + }, + ), ], ); }