diff --git a/apps/web/src/assets/i18n/ar.json b/apps/web/src/assets/i18n/ar.json index 554b38e78..51fbb71dd 100644 --- a/apps/web/src/assets/i18n/ar.json +++ b/apps/web/src/assets/i18n/ar.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "إجراءات الاسترداد الموصى بها", "ACTION_TRY_PLAYER": "جرّب {{player}}", "ACTION_TRY_PLAYER_HINT": "مؤقت لهذا العنصر؛ سيبقى المشغل المحفوظ دون تغيير", diff --git a/apps/web/src/assets/i18n/ary.json b/apps/web/src/assets/i18n/ary.json index 8053ce6fa..5013aff40 100644 --- a/apps/web/src/assets/i18n/ary.json +++ b/apps/web/src/assets/i18n/ary.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "إجراءات الاسترجاع المقترحة", "ACTION_TRY_PLAYER": "جرّب {{player}}", "ACTION_TRY_PLAYER_HINT": "مؤقت غير لهاد المحتوى؛ المشغل المحفوظ غيبقى بلا تغيير", diff --git a/apps/web/src/assets/i18n/by.json b/apps/web/src/assets/i18n/by.json index 5f0ac42df..d58c9a6fb 100644 --- a/apps/web/src/assets/i18n/by.json +++ b/apps/web/src/assets/i18n/by.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Рэкамендаваныя дзеянні для аднаўлення", "ACTION_TRY_PLAYER": "Паспрабаваць {{player}}", "ACTION_TRY_PLAYER_HINT": "Часова толькі для гэтага элемента; захаваны плэер не зменіцца", diff --git a/apps/web/src/assets/i18n/de.json b/apps/web/src/assets/i18n/de.json index 549d8b36a..3048c6f9f 100644 --- a/apps/web/src/assets/i18n/de.json +++ b/apps/web/src/assets/i18n/de.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Empfohlene Wiederherstellungsaktionen", "ACTION_TRY_PLAYER": "{{player}} ausprobieren", "ACTION_TRY_PLAYER_HINT": "Nur vorübergehend für diesen Inhalt; der gespeicherte Player bleibt unverändert", diff --git a/apps/web/src/assets/i18n/el.json b/apps/web/src/assets/i18n/el.json index 43d522514..99fca591f 100644 --- a/apps/web/src/assets/i18n/el.json +++ b/apps/web/src/assets/i18n/el.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Προτεινόμενες ενέργειες ανάκτησης", "ACTION_TRY_PLAYER": "Δοκιμή του {{player}}", "ACTION_TRY_PLAYER_HINT": "Προσωρινά για αυτό το στοιχείο· το αποθηκευμένο πρόγραμμα αναπαραγωγής δεν αλλάζει", diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index b118311ea..ce788e57d 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Recommended recovery actions", "ACTION_TRY_PLAYER": "Try {{player}}", "ACTION_TRY_PLAYER_HINT": "Temporary for this item; your saved player stays unchanged", diff --git a/apps/web/src/assets/i18n/es.json b/apps/web/src/assets/i18n/es.json index 6c73270c1..729cf98cf 100644 --- a/apps/web/src/assets/i18n/es.json +++ b/apps/web/src/assets/i18n/es.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Acciones de recuperación recomendadas", "ACTION_TRY_PLAYER": "Probar {{player}}", "ACTION_TRY_PLAYER_HINT": "Temporal para este elemento; el reproductor guardado no cambiará", diff --git a/apps/web/src/assets/i18n/fr.json b/apps/web/src/assets/i18n/fr.json index 01c7e269b..bbac35b49 100644 --- a/apps/web/src/assets/i18n/fr.json +++ b/apps/web/src/assets/i18n/fr.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Actions de récupération recommandées", "ACTION_TRY_PLAYER": "Essayer {{player}}", "ACTION_TRY_PLAYER_HINT": "Temporaire pour cet élément ; votre lecteur enregistré reste inchangé", diff --git a/apps/web/src/assets/i18n/hu.json b/apps/web/src/assets/i18n/hu.json index 2e8699b8c..50f9bf2fa 100644 --- a/apps/web/src/assets/i18n/hu.json +++ b/apps/web/src/assets/i18n/hu.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "A böngészőben működő lejátszó nem tudott hozzáférni ehhez az adatfolyamhoz", "DESCRIPTION": "A kérést CORS-korlátozás, vegyes tartalom, illetve a szolgáltató fejléc- vagy proxykorlátozásai akadályozhatják. Az MPV vagy a VLC gyakran képes megkerülni a böngészőben működő lejátszó hozzáférési korlátait.", - "PWA_DESCRIPTION": "A kérést CORS-korlátozás, vegyes tartalom, illetve a szolgáltató fejléc- vagy proxykorlátozásai akadályozhatják. A PWA nem tud közvetlenül külső lejátszót indítani; válassza az Adatfolyam URL-címének másolása műveletet, majd nyissa meg kézzel MPV-ben, VLC-ben, IINA-ban vagy más lejátszóban." + "PWA_DESCRIPTION": "A kérést CORS-korlátozás, vegyes tartalom, illetve a szolgáltató fejléc- vagy proxykorlátozásai akadályozhatják. A PWA nem tud közvetlenül külső lejátszót indítani; válassza az Adatfolyam URL-címének másolása műveletet, majd nyissa meg kézzel MPV-ben, VLC-ben, IINA-ban vagy más lejátszóban.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "Az adatfolyam titkosított vagy DRM-védelemmel ellátott lehet", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Nyissa meg inkább külső lejátszóban", "INLINE_FAILURE_TITLE": "Másolja ki az URL-címet, vagy próbálja újra", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Ajánlott helyreállítási műveletek", "ACTION_TRY_PLAYER": "A(z) {{player}} kipróbálása", "ACTION_TRY_PLAYER_HINT": "Csak ennél az elemnél ideiglenes; a mentett lejátszó változatlan marad", diff --git a/apps/web/src/assets/i18n/it.json b/apps/web/src/assets/i18n/it.json index 37de21d9b..0f54e8ba0 100644 --- a/apps/web/src/assets/i18n/it.json +++ b/apps/web/src/assets/i18n/it.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Azioni di ripristino consigliate", "ACTION_TRY_PLAYER": "Prova {{player}}", "ACTION_TRY_PLAYER_HINT": "Temporaneo per questo elemento; il lettore salvato resta invariato", diff --git a/apps/web/src/assets/i18n/ja.json b/apps/web/src/assets/i18n/ja.json index c233a8544..a947ebaed 100644 --- a/apps/web/src/assets/i18n/ja.json +++ b/apps/web/src/assets/i18n/ja.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "推奨される復旧操作", "ACTION_TRY_PLAYER": "{{player}} を試す", "ACTION_TRY_PLAYER_HINT": "この項目にのみ一時的に適用され、保存済みのプレーヤーは変更されません", diff --git a/apps/web/src/assets/i18n/ko.json b/apps/web/src/assets/i18n/ko.json index bbbb39678..d596d3781 100644 --- a/apps/web/src/assets/i18n/ko.json +++ b/apps/web/src/assets/i18n/ko.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "권장 복구 작업", "ACTION_TRY_PLAYER": "{{player}} 사용해 보기", "ACTION_TRY_PLAYER_HINT": "이 항목에만 임시로 적용되며 저장된 플레이어는 변경되지 않습니다", diff --git a/apps/web/src/assets/i18n/nl.json b/apps/web/src/assets/i18n/nl.json index d5482d88e..dda1694d0 100644 --- a/apps/web/src/assets/i18n/nl.json +++ b/apps/web/src/assets/i18n/nl.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Aanbevolen herstelacties", "ACTION_TRY_PLAYER": "{{player}} proberen", "ACTION_TRY_PLAYER_HINT": "Tijdelijk voor dit item; je opgeslagen speler blijft ongewijzigd", diff --git a/apps/web/src/assets/i18n/pl.json b/apps/web/src/assets/i18n/pl.json index 476928f2f..fcd7ee858 100644 --- a/apps/web/src/assets/i18n/pl.json +++ b/apps/web/src/assets/i18n/pl.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Zalecane działania odzyskiwania", "ACTION_TRY_PLAYER": "Wypróbuj {{player}}", "ACTION_TRY_PLAYER_HINT": "Tymczasowo dla tego elementu; zapisany odtwarzacz pozostanie bez zmian", diff --git a/apps/web/src/assets/i18n/pt.json b/apps/web/src/assets/i18n/pt.json index a097ccfd9..b03e403de 100644 --- a/apps/web/src/assets/i18n/pt.json +++ b/apps/web/src/assets/i18n/pt.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Ações de recuperação recomendadas", "ACTION_TRY_PLAYER": "Experimentar {{player}}", "ACTION_TRY_PLAYER_HINT": "Temporário para este item; o reprodutor salvo permanece inalterado", diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json index 7719dfe9a..a86a86e00 100644 --- a/apps/web/src/assets/i18n/ru.json +++ b/apps/web/src/assets/i18n/ru.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "Браузерный плеер не получил доступ к потоку", "DESCRIPTION": "Запрос может быть заблокирован CORS, mixed content или требованиями провайдера/прокси к заголовкам. MPV или VLC часто обходят такие ограничения браузерного плеера.", - "PWA_DESCRIPTION": "Запрос может быть заблокирован CORS, mixed content или требованиями провайдера/прокси к заголовкам. PWA не может запускать внешние плееры напрямую: нажмите «Скопировать URL потока» и откройте его вручную в MPV, VLC, IINA или другом плеере." + "PWA_DESCRIPTION": "Запрос может быть заблокирован CORS, mixed content или требованиями провайдера/прокси к заголовкам. PWA не может запускать внешние плееры напрямую: нажмите «Скопировать URL потока» и откройте его вручную в MPV, VLC, IINA или другом плеере.", + "UNTRANSFERABLE_DESCRIPTION": "Для этого защищённого потока требуются настройки воспроизведения, которые нельзя передать вместе с URL-адресом потока." }, "DRM_OR_ENCRYPTION": { "TITLE": "Поток может быть зашифрован или защищен DRM", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Откройте поток во внешнем плеере", "INLINE_FAILURE_TITLE": "Скопируйте URL или повторите попытку", + "UNTRANSFERABLE_FAILURE_TITLE": "Не удалось продолжить защищённое воспроизведение", "RECOMMENDATIONS_LABEL": "Рекомендуемые действия для восстановления", "ACTION_TRY_PLAYER": "Попробовать {{player}}", "ACTION_TRY_PLAYER_HINT": "Временно только для этого элемента; сохранённый плеер не изменится", diff --git a/apps/web/src/assets/i18n/tr.json b/apps/web/src/assets/i18n/tr.json index 4098a631f..5b7bc4c7c 100644 --- a/apps/web/src/assets/i18n/tr.json +++ b/apps/web/src/assets/i18n/tr.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "Önerilen kurtarma işlemleri", "ACTION_TRY_PLAYER": "{{player}} ile dene", "ACTION_TRY_PLAYER_HINT": "Bu öğe için geçicidir; kayıtlı oynatıcınız değişmeden kalır", diff --git a/apps/web/src/assets/i18n/zh.json b/apps/web/src/assets/i18n/zh.json index 450960909..f44d6187e 100644 --- a/apps/web/src/assets/i18n/zh.json +++ b/apps/web/src/assets/i18n/zh.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "推荐的恢复操作", "ACTION_TRY_PLAYER": "尝试 {{player}}", "ACTION_TRY_PLAYER_HINT": "仅临时用于此项目;已保存的播放器不会更改", diff --git a/apps/web/src/assets/i18n/zhtw.json b/apps/web/src/assets/i18n/zhtw.json index b01cb70b1..516b009c9 100644 --- a/apps/web/src/assets/i18n/zhtw.json +++ b/apps/web/src/assets/i18n/zhtw.json @@ -549,7 +549,8 @@ "BROWSER_ACCESS_ERROR": { "TITLE": "The browser player could not access this stream", "DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. MPV or VLC can often bypass browser-player access limits.", - "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player." + "PWA_DESCRIPTION": "The request may be blocked by CORS, mixed content, or provider header/proxy restrictions. The PWA cannot launch external players directly; use the Copy stream URL action and open it manually in MPV, VLC, IINA, or another player.", + "UNTRANSFERABLE_DESCRIPTION": "This protected stream requires playback configuration that cannot be transferred with the stream URL." }, "DRM_OR_ENCRYPTION": { "TITLE": "This stream may be encrypted or DRM-protected", @@ -561,6 +562,7 @@ }, "NATIVE_FALLBACK_TITLE": "Open it in a native player instead", "INLINE_FAILURE_TITLE": "Copy the URL or try again", + "UNTRANSFERABLE_FAILURE_TITLE": "Protected playback could not continue", "RECOMMENDATIONS_LABEL": "建議的復原操作", "ACTION_TRY_PLAYER": "嘗試 {{player}}", "ACTION_TRY_PLAYER_HINT": "僅暫時套用於此項目;已儲存的播放器不會變更", diff --git a/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-panel.component.spec.ts b/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-panel.component.spec.ts index 1bfc0e432..7ca962fff 100644 --- a/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-panel.component.spec.ts +++ b/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-panel.component.spec.ts @@ -147,6 +147,7 @@ describe('PlaybackDiagnosticPanelComponent', () => { fixture.componentRef.setInput('recommendations', []); fixture.componentRef.setInput('playback', PLAYBACK); fixture.componentRef.setInput('supportsManagedExternalPlayers', false); + fixture.componentRef.setInput('playbackExternallyTransferable', true); }); it('applies the component host style from the actual panel stylesheet', () => { @@ -496,12 +497,16 @@ describe('PlaybackDiagnosticPanelComponent', () => { ); }); - it('does not infer external recovery copy for DRM without a ranked external target', () => { + it('uses a neutral headline for untransferable DRM without a ranked external target', () => { fixture.componentRef.setInput('diagnostic', { ...DIAGNOSTIC, code: PlaybackDiagnosticCode.DrmOrEncryption, }); fixture.componentRef.setInput('supportsManagedExternalPlayers', true); + fixture.componentRef.setInput( + 'playbackExternallyTransferable', + false + ); fixture.componentRef.setInput('recommendations', [ recommendation('alternative-source', { priority: 'primary' }), ]); @@ -509,7 +514,7 @@ describe('PlaybackDiagnosticPanelComponent', () => { fixture.detectChanges(); expect(fixture.nativeElement.textContent).toContain( - 'PLAYBACK_DIAGNOSTICS.INLINE_FAILURE_TITLE' + 'PLAYBACK_DIAGNOSTICS.UNTRANSFERABLE_FAILURE_TITLE' ); expect(fixture.nativeElement.textContent).not.toContain( 'PLAYBACK_DIAGNOSTICS.NATIVE_FALLBACK_TITLE' diff --git a/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-panel.component.ts b/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-panel.component.ts index 83cbf9f54..8a176a8f3 100644 --- a/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-panel.component.ts +++ b/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-panel.component.ts @@ -60,6 +60,7 @@ export class PlaybackDiagnosticPanelComponent { input.required(); readonly playback = input.required(); readonly supportsManagedExternalPlayers = input.required(); + readonly playbackExternallyTransferable = input.required(); readonly alternativeSources = input([]); readonly pending = input(false); @@ -80,11 +81,14 @@ export class PlaybackDiagnosticPanelComponent { readonly hasExternalPlayerRecommendation = computed(() => this.recommendations().some(isExternalPlayerRecommendation) ); - readonly diagnosticHeadlineKey = computed(() => - this.hasExternalPlayerRecommendation() - ? 'PLAYBACK_DIAGNOSTICS.NATIVE_FALLBACK_TITLE' - : 'PLAYBACK_DIAGNOSTICS.INLINE_FAILURE_TITLE' - ); + readonly diagnosticHeadlineKey = computed(() => { + if (this.hasExternalPlayerRecommendation()) { + return 'PLAYBACK_DIAGNOSTICS.NATIVE_FALLBACK_TITLE'; + } + return this.playbackExternallyTransferable() + ? 'PLAYBACK_DIAGNOSTICS.INLINE_FAILURE_TITLE' + : 'PLAYBACK_DIAGNOSTICS.UNTRANSFERABLE_FAILURE_TITLE'; + }); readonly getDiagnosticTitleKey = getDiagnosticTitleKey; readonly getDiagnosticMeta = getDiagnosticMeta; @@ -102,7 +106,8 @@ export class PlaybackDiagnosticPanelComponent { getDiagnosticDescriptionKey(issue: PlaybackDiagnostic): string { return getDiagnosticDescriptionKey( issue, - this.supportsManagedExternalPlayers() + this.supportsManagedExternalPlayers(), + this.playbackExternallyTransferable() ); } diff --git a/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-view.util.spec.ts b/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-view.util.spec.ts index 94e9c778a..c933644a3 100644 --- a/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-view.util.spec.ts +++ b/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-view.util.spec.ts @@ -43,17 +43,50 @@ describe('playback diagnostic view formatters', () => { ); }); - it('uses external recommendation presence for browser-access copy', () => { + it.each([ + { + runtime: 'desktop', + supportsManagedExternalPlayers: true, + externallyTransferable: true, + expected: + 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.DESCRIPTION', + }, + { + runtime: 'PWA', + supportsManagedExternalPlayers: false, + externallyTransferable: true, + expected: + 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.PWA_DESCRIPTION', + }, + { + runtime: 'desktop with protected playback', + supportsManagedExternalPlayers: true, + externallyTransferable: false, + expected: + 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.UNTRANSFERABLE_DESCRIPTION', + }, + { + runtime: 'PWA with protected playback', + supportsManagedExternalPlayers: false, + externallyTransferable: false, + expected: + 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.UNTRANSFERABLE_DESCRIPTION', + }, + ])('uses $runtime browser-access copy', ({ + supportsManagedExternalPlayers, + externallyTransferable, + expected, + }) => { const browserIssue: PlaybackDiagnostic = { ...issue, code: PlaybackDiagnosticCode.BrowserAccessError, }; - - expect(getDiagnosticDescriptionKey(browserIssue, true)).toBe( - 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.DESCRIPTION' - ); - expect(getDiagnosticDescriptionKey(browserIssue, false)).toBe( - 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.PWA_DESCRIPTION' - ); + expect( + getDiagnosticDescriptionKey( + browserIssue, + supportsManagedExternalPlayers, + externallyTransferable + ) + ).toBe(expected); }); }); diff --git a/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-view.util.ts b/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-view.util.ts index 966c437b8..23384cacf 100644 --- a/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-view.util.ts +++ b/libs/ui/playback/src/lib/playback-diagnostic-panel/playback-diagnostic-view.util.ts @@ -15,8 +15,16 @@ export function getDiagnosticTitleKey(issue: PlaybackDiagnostic): string { export function getDiagnosticDescriptionKey( issue: PlaybackDiagnostic, - supportsManagedExternalPlayers: boolean + supportsManagedExternalPlayers: boolean, + playbackExternallyTransferable: boolean ): string { + if ( + issue.code === PlaybackDiagnosticCode.BrowserAccessError && + !playbackExternallyTransferable + ) { + return 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.UNTRANSFERABLE_DESCRIPTION'; + } + if ( issue.code === PlaybackDiagnosticCode.BrowserAccessError && !supportsManagedExternalPlayers diff --git a/libs/ui/playback/src/lib/web-player-view/web-player-recovery-policy.ts b/libs/ui/playback/src/lib/web-player-view/web-player-recovery-policy.ts index 3e5fceff7..c97ecd3ce 100644 --- a/libs/ui/playback/src/lib/web-player-view/web-player-recovery-policy.ts +++ b/libs/ui/playback/src/lib/web-player-view/web-player-recovery-policy.ts @@ -18,7 +18,7 @@ export function createWebPlayerRecommendations(options: { readonly binding: PlaybackBinding | null; readonly attemptedTargets: ReadonlySet; readonly managedExternalPlayersAvailable: boolean; - readonly playback: ResolvedPortalPlayback; + readonly playbackExternallyTransferable: boolean; readonly isLive: boolean; readonly alternativeSourceCount: number; }): readonly PlaybackRecommendation[] { @@ -26,7 +26,6 @@ export function createWebPlayerRecommendations(options: { return []; } const sourceKind = resolvePlaybackSourceKind(options.diagnostic); - const drm = options.playback.drm; return recommendPlaybackRecovery({ diagnostic: options.diagnostic, activeTarget: options.binding.target, @@ -39,13 +38,21 @@ export function createWebPlayerRecommendations(options: { source: { kind: sourceKind, isLive: options.isLive, - drm: drm ? 'untransferable' : 'none', - externalTransferable: !drm, + drm: options.playbackExternallyTransferable + ? 'none' + : 'untransferable', + externalTransferable: options.playbackExternallyTransferable, }, alternativeSourceCount: options.alternativeSourceCount, }); } +export function isPlaybackExternallyTransferable( + playback: ResolvedPortalPlayback +): boolean { + return playback.drm === undefined; +} + export function toVideoPlayer(target: InlinePlaybackPlayer): VideoPlayer { switch (target) { case InlinePlaybackPlayer.VideoJs: diff --git a/libs/ui/playback/src/lib/web-player-view/web-player-view.component.html b/libs/ui/playback/src/lib/web-player-view/web-player-view.component.html index d42b684fa..b55da54d0 100644 --- a/libs/ui/playback/src/lib/web-player-view/web-player-view.component.html +++ b/libs/ui/playback/src/lib/web-player-view/web-player-view.component.html @@ -101,6 +101,7 @@ [recommendations]="recommendations()" [playback]="resolvedPlayback()" [supportsManagedExternalPlayers]="supportsManagedExternalPlayers" + [playbackExternallyTransferable]="playbackExternallyTransferable()" [alternativeSources]="alternativeSources()" [pending]="recoveryPending()" (retryRequested)="retryPlayback()" diff --git a/libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts b/libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts index ba24a270c..89e09e84d 100644 --- a/libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts +++ b/libs/ui/playback/src/lib/web-player-view/web-player-view.component.spec.ts @@ -328,6 +328,44 @@ describe('WebPlayerViewComponent', () => { ); }); + it('uses neutral browser-access guidance when DRM cannot be transferred', () => { + runtimeCapabilities.supportsManagedExternalPlayers = true; + fixture.componentRef.setInput('playback', { + streamUrl: 'https://provider.example/protected.mpd', + title: 'Protected stream', + drm: { + licenseType: 'clearkey', + supported: true, + clearKeys: { + '00112233445566778899aabbccddeeff': + 'ffeeddccbbaa99887766554433221100', + }, + }, + }); + + fixture.detectChanges(); + emitPlaybackIssue(createBrowserAccessDiagnostic()); + fixture.detectChanges(); + + const banner = fixture.debugElement.query( + By.css('[data-test-id="playback-diagnostic-banner"]') + ); + + expect(banner.nativeElement.textContent).toContain( + 'PLAYBACK_DIAGNOSTICS.UNTRANSFERABLE_FAILURE_TITLE' + ); + expect(banner.nativeElement.textContent).toContain( + 'PLAYBACK_DIAGNOSTICS.BROWSER_ACCESS_ERROR.UNTRANSFERABLE_DESCRIPTION' + ); + expect( + banner.query( + By.css( + '[data-test-id="playback-fallback-mpv"], [data-test-id="playback-fallback-vlc"]' + ) + ) + ).toBeNull(); + }); + it('renders only sanitized structured HLS evidence in technical details', () => { const issue = createStructuredHlsDiagnostic(); @@ -1004,7 +1042,8 @@ describe('WebPlayerViewComponent', () => { function getDiagnosticDescriptionKey(issue: PlaybackDiagnostic): string { return resolveDiagnosticDescriptionKey( issue, - runtimeCapabilities.supportsManagedExternalPlayers + runtimeCapabilities.supportsManagedExternalPlayers, + true ); } }); diff --git a/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts b/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts index 7ecf1c092..a123a5092 100644 --- a/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts +++ b/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts @@ -54,6 +54,7 @@ import { import { resolveWebPlayerMediaTitle } from './web-player-playback-state'; import { createWebPlayerRecommendations, + isPlaybackExternallyTransferable, toInlinePlaybackPlayer, toVideoPlayer, } from './web-player-recovery-policy'; @@ -178,6 +179,9 @@ export class WebPlayerViewComponent implements OnDestroy { readonly resolvedMediaTitle = computed(() => resolveWebPlayerMediaTitle(this.mediaTitle(), this.resolvedPlayback()) ); + readonly playbackExternallyTransferable = computed(() => + isPlaybackExternallyTransferable(this.resolvedPlayback()) + ); readonly recordingFolder = computed( () => this.settings()?.recordingFolder ?? '' ); @@ -196,7 +200,8 @@ export class WebPlayerViewComponent implements OnDestroy { attemptedTargets: this.recoverySession.attemptedTargets(), managedExternalPlayersAvailable: this.runtime.supportsManagedExternalPlayers, - playback: this.resolvedPlayback(), + playbackExternallyTransferable: + this.playbackExternallyTransferable(), isLive: this.resolvedIsLive(), alternativeSourceCount: this.alternativeSources().length, });