From ca3c5e8de9f78248579d9a05064714fc045c98af Mon Sep 17 00:00:00 2001 From: American_Jesus Date: Wed, 17 Dec 2025 19:12:57 +0000 Subject: [PATCH 01/45] Translated using Weblate (Portuguese) Currently translated at 100.0% (314 of 314 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt/ --- app/src/main/res/values-pt/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 89c4b105..5abc23a0 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -371,4 +371,7 @@ Ordenar canais por visualizações recentes Codificação de cores para os programas Atraso da legenda + Insira o endereço do servidor + Nenhum servidor encontrado + Estilo de fundo From 7930b1d4d03af28828075dbc5a298328bd87069f Mon Sep 17 00:00:00 2001 From: arcker95 Date: Thu, 18 Dec 2025 00:31:25 +0000 Subject: [PATCH 02/45] Translated using Weblate (Italian) Currently translated at 100.0% (314 of 314 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index faf958b2..61a7b11b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -371,4 +371,7 @@ Ordina canali per ultimo guardato Codifica colori per i programmi Ritardo sottotitoli + Inserisci indirizzo server + Nessun server trovato + Stile backdrop From 05fffe63a2984adebede1a42dd19233aeb46f5a0 Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 18 Dec 2025 01:05:58 +0000 Subject: [PATCH 03/45] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (314 of 314 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2d317635..674948ad 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -339,4 +339,7 @@ 按最近观看的频道排序 节目颜色标记 字幕延迟 + 输入服务器地址 + 未找到服务器 + 背景幕样式 From dbe8074997264474e9f9fde1833c81ba52e10829 Mon Sep 17 00:00:00 2001 From: oyvhov Date: Thu, 18 Dec 2025 10:28:41 +0000 Subject: [PATCH 04/45] Translated using Weblate (Norwegian Nynorsk) Currently translated at 8.2% (26 of 314 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/nn/ --- app/src/main/res/values-nn/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index fd8c6cce..1813b6af 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -21,4 +21,5 @@ Slett Dødd Regissert av %1$s + Eininga støttar AC3/Dolby Digital From 76db702564f00c42ddebedfb840e1e8cbe8c55a9 Mon Sep 17 00:00:00 2001 From: SimonHung Date: Thu, 18 Dec 2025 18:40:38 +0000 Subject: [PATCH 05/45] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 87.2% (274 of 314 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ef0da683..511340df 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -299,4 +299,7 @@ 使用帳號/密碼 登入 字幕同步 + 顯示標題 + 輸入伺服器位址 + 未找到伺服器 From 955d5776241ce6a668ebf3cd243bfe1a13435f0b Mon Sep 17 00:00:00 2001 From: oyvhov Date: Thu, 18 Dec 2025 10:37:20 +0000 Subject: [PATCH 06/45] Translated using Weblate (Norwegian Nynorsk) Currently translated at 100.0% (314 of 314 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/nn/ --- app/src/main/res/values-nn/strings.xml | 342 ++++++++++++++++++++++++- 1 file changed, 339 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index 1813b6af..a2fbd5b8 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -3,12 +3,12 @@ Om Aktive opptak Favoritt - Legg til Server - Legg til Brukar + Legg til server + Legg til brukar Lyd Fødestad Bitrate - Født + Fødd Avbryt opptak Avbryt opptak av serie Avbryt @@ -22,4 +22,340 @@ Dødd Regissert av %1$s Eininga støttar AC3/Dolby Digital + Tøm biletmellomlager + Reklame + Brukarvurdering + Ein feil har oppstått! Trykk på knappen for å sende loggar til servaren din. + Hald fram med å sjå + Meldevurdering + %.1f sekund + Regissør + Deaktivert + Funne servarar + + Lastar ned… + Aktivert + Oppgi servar-IP eller URL + Oppgi servaradresse + Episodar + Feil ved lasting av samling %1$s + Ekstern + Favorittar + Storleik + Tvungen + Sjangrar + Gå til serie + Gå til + Gøym avspelingskontrollar + Gøym feilsøkingsinfo + Gøym + Heim + Omgåande + Intro + #ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ + Bibliotek + Lisensinformasjon + Direkte-TV + Lastar… + Marker heile serien som sett? + Marker heile serien som usett? + Marker som usett + Marker som sett + Maks bitrate + Meir som dette + Meir + Filmar + Namn + Neste + Ingen data + Ingen resultat + Ingen servarar funne + Ingen planlagde opptak + Ingen oppdatering tilgjengeleg + Outro + Filveg + Folk + Antall avspelingar + Spel av herfrå + Spel av + Vis feilsøkingsinfo for avspeling + Avspelingsfart + Avspeling + Speleliste + Spelelister + Førehandsvising + Innstillingar for brukarprofil + + Samandrag + Sist lagt til i %1$s + Sist lagt til + Sist tatt opp + Sist utgitt + Tilrådd + Ta opp program + Ta opp serie + Fjern favoritt + Start på nytt + Hald fram + Lagre + + Søk + Søker… + Vel servar + Vel brukar + Vis feilsøkingsinfo + Vis \'Neste\' + Serie + Tilfeldig rekkjefølgje + Hopp over + Dato lagt til + Dato episode lagt til + Dato spela av + Utginningsdato + Namn + Tilfeldig + Studio + Send inn + Undertekst + Undertekstar + Forslag + Byt servar + Byt brukar + Best vurderte usette + Trailer + + Trailerar + + + DVR-plan + Programoversikt + Sesong + Sesongar + TV-seriar + Grensesnitt + Ukjend + Oppdateringar + Versjon + Videoskalering + Video + Videoar + Sjå direkte + %1$d år gammal + Spel av med transkoding + Mediainformasjon + Vis klokke + Rekkjefølgje etter sendedato + Lag ny speleliste + Legg til i speleliste + Pause med eitt klikk + Trykk i midten på styreputa for å pause/spele av + Kursiv skrift + Skrifttype + Bakgrunn + Fullført + Aldersgrense + Speltid + Ekstra + + Anna + + + + Bak kulissane + + + + Temasongar + + + + Temavideoar + + + + Klipp + + + + Sletta sener + + + + Intervju + + + + Scener + + + + Smakebitar + + + + Bakomfilmar + + + + Kortfilmar + + + Avanserte innstillingar + Avansert grensesnitt + App-tema + Sjekk automatisk etter oppdateringar + Forseinking før neste vert spela av + Spel neste automatisk + Sjekk etter oppdateringar + Gjeld berre TV-seriar + + Direkteavspeling av ASS-undertekstar + Direkteavspeling av PGS-undertekstar + Alltid nedmiks til stereo + Bruk FFmpeg-dekodermodul + Standard innhaldsskalering + Installer oppdatering + Installert versjon + Maks element på rader på heimskjermen + Tilpass element i navigasjonsskuffa + Klikk for å byte side + Byt side i navigasjonsskuffa ved fokus + Svevn-beskyttelse + Spel temamusikk + Overstyring av avspeling + Hugs valde faner + Tillat gjensjåing i \'Neste\' + Steglengde for søkelinje + Nyttig for feilsøking + Send app-loggar til gjeldande servar + Vil prøve å sende til sist tilkopla servar + Send krasjrapportar + Innstillingar + Hopp tilbake ved framhald av avspeling + Hopp tilbake + Handling for overhopping av reklame + Hopp framover + Handling for overhopping av intro + Handling for overhopping av outro + Handling for overhopping av førehandsvising + Handling for overhopping av samandrag + Oppdatering tilgjengeleg + URL for sjekk av app-oppdateringar + URL for oppdatering + Skriftstorleik + Skriftfarge + Gjennomsiktigheit for skrift + Kantstil + Kantfarge + Gjennomsiktigheit for bakgrunn + Bakgrunnsstil + Stil for undertekst + Bakgrunnsfarge + Nullstill + Feit skrift + Avspelingsmotor + MPV: Bruk maskinvaredekoding + Deaktiver dersom appen krasjar + MPV-val + ExoPlayer-val + Hopp over segment + Hopp over reklame + Hopp over førehandsvising + Hopp over samandrag + Hopp over outro + Hopp over intro + Sett + Filter + År + Tiår + Fjern + Dolby Vision + Dolby Atmos + MPV: Bruk gpu-next + Rediger mpv.conf + Forkast endringar? + Marg + Detaljert logging + Tast inn PIN + Logg inn automatisk + Logg inn via servar + Trykk i midten for å bekrefte + Krev PIN for profil + Bekreft PIN + Feil + PIN må vere 4 tastar eller lenger + Vil fjerne PIN + Storleik på biletmellomlager (MB) + Visingsval + Kolonner + Mellomrom + Bileteforhold + Vis detaljar + Bilettype + + Gjesteroller + Kantstorleik + Byting av bildeoppdateringsfrekvens + Automatisk + Bruk brukarnamn/passord + Logg inn + Generelt + Containar + Tittel + Kodek + Profil + Nivå + Oppløysing + Anamorfisk + Linjefletta + Bildefrekvens + Bit-dybde + Videoområde + Type videoområde + Fargerom + Fargeoverføring + Fargeprimærar + Pikselformat + Referanserammer + NAL + Språk + Layout + Kanalar + Samplingsfrekvens + AVC + Ja + Nei + SDR + HDR + HDR10 + HDR10+ + HLG + bit + Hz + Vis titlar + Repeter + Vis favorittkanalar først + Sorter kanalar etter sist sett + Fargekod program + Undertekstforseinking + Bakgrunnsstil + Ingen + Nedlastinga tek lang tid, du må kanskje starte avspelinga på nytt + + %s nedlasting + %s nedlastingar + + + %d time + %d timar + + + %d element + %d elementer + + + %d sekund + %d sekund + + Vel standardelementa som skal visast, andre vert skjulde From 1289bed0c44c299fb65d90b64608c0cf8e9d082b Mon Sep 17 00:00:00 2001 From: SimonHung Date: Thu, 18 Dec 2025 19:27:51 +0000 Subject: [PATCH 07/45] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 88.5% (278 of 314 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 511340df..0130d2aa 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -302,4 +302,8 @@ 顯示標題 輸入伺服器位址 未找到伺服器 + 背景圖樣式 + 節目顏色標記 + 優先顯示最愛頻道 + 按最近觀看的頻道排序 From 225b3c0455b919d9ca7428368357c50fdf06a032 Mon Sep 17 00:00:00 2001 From: lazigeri Date: Thu, 18 Dec 2025 22:44:18 +0000 Subject: [PATCH 08/45] Translated using Weblate (German) Currently translated at 76.8% (242 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/ --- app/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 13f608d7..1d4610a6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -239,7 +239,7 @@ Nützlich zum Debuggen Sende Absturzmeldung Zurückspringen wenn wiedergabe fortgesetzt wird - Überspringe Werbung verhalten + Werbung überspringen Vorspringen Überspringe Intro verhalten Überspringe Outro verhalten From ff8a9a7125c9be70959a448b336e7c2920d75df3 Mon Sep 17 00:00:00 2001 From: lazigeri Date: Thu, 18 Dec 2025 23:20:52 +0000 Subject: [PATCH 09/45] Translated using Weblate (Hungarian) Currently translated at 78.7% (248 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 109 ++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 7d0320bb..83e75405 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -123,7 +123,7 @@ Előzetes Előzetesek - + DVR Időzítés Műsorújság @@ -155,47 +155,47 @@ Extrák Egyéb - + Színfalak mögött - + Téma zenék - + Téma videók - + Klippek - + Törölt jelenetek - + Interjúk - + Jelenetek - + Minták - + Rövidfilmek - + Rövidek - + %s letöltés @@ -213,4 +213,89 @@ %d másodperc %d másodpercek + Véget ér ekkor: %1$s + Nem található szerver + Visszatekintés + Média Információ + A készülék támogatja az AC3/Dolby Digital formátumot + Haladó Beállítások + Haladó Felhasználói Felület + Alkalmazás Témája + Frissítések automatikus keresése + Késleltetés a következő elem lejátszása előtt + Következő elem automatikus lejátszása + Frissítések keresése + Csak a sorozatokra vonatkzik + ASS feliratok közvetlen lejátszása + PGS feliratok közvetlen lejátszása + Mindig konvertálja sztereóvá + Használja az FFmpeg dekóder modulját + A tartalom alapértelmezett mérete + Frissítés telepítése + Telepített verzió + Elemek maximális száma a kezdőlapi sorokon + Válaszd ki az alapértelmezett elemeket, amelyek megjelennek, a többi el lesz rejtve + Navigációs Fiók elemeinek testreszabása + Klikkelj az oldalak közti váltáshoz + Automatikus leállítás inaktivitás esetén + Főcímdal lejátszása + Kiválasztott lapok megjegyzése + Újranézés engedélyezése a Következő menüben + Előre/hátra ugrás intervallum + Hibakereséshez hasznos + Naplófájlok küldése a jelenlegi szerverre + Megpróbálja elküldeni az legutóbb csatlakozott szerverre + Összeomlási napló küldése + Beállítások + Visszaugrás a lejátszás folytatásakor + Visszaugrás + Reklámok átugrása + Előreugrás + Intró átugrása + Befejezés átugrása + Epizódelőzetesek átugrása + Összefoglaló átugrása + Frissítés érhető el + Az alkalmazás frissítéseinek ellenőrzésére használt URL + Frissítés URL + Betűméret + Betűszín + Betűk áttetszősége + Szegély stílusa + Szegély színe + Háttér áttetszősége + Háttér stílusa + Felirat stílusa + Háttér színe + Visszaállítás + Félkövér betűk + MPV: hardveres dekódolás használata + Tiltsd le, ha összeomlást tapasztalsz + MPV beállítások + ExoPlayer beállítások + Szegmens átugrása + Reklám átugrása + Előzetes átugrása + Összefoglaló átugrása + Stáblista átugrása + Intró átugrása + Lejátszott + Szűrő + Év + Évtized + Eltávolítás + Dolby Vision + Dolby Atmos + MPV: gpu-next használata + mpv.conf szerkesztése + Változások elvetése? + Margók + Részletes naplózás + Adja meg a PIN-kódot + Automatikus bejelentkezés + Bejelentkezés szerveren keresztül + Nyomja meg a középső gombot a megerősítéshez + Kérjen PIN-kódot a profillal történő bejelentkezéshez + PIN-kód megerősítése + Helytelen From dfa1ba079c168b4bcf6c577721e8f5b5ed2a0226 Mon Sep 17 00:00:00 2001 From: lazigeri Date: Thu, 18 Dec 2025 23:26:06 +0000 Subject: [PATCH 10/45] Translated using Weblate (Hungarian) Currently translated at 81.5% (257 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 83e75405..5130a5a3 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -298,4 +298,14 @@ Kérjen PIN-kódot a profillal történő bejelentkezéshez PIN-kód megerősítése Helytelen + A PIN-kódnak legalább 4 karakterből kell állnia + PIN-kód eltávolítása + Nézet beállítások + Oszlopok + Helykihagyás + Képarány + Részletek mutatása + Kép típusa + Vendégszereplők + Szegély mérete From 8d4fcb377d4a9ff5269ee6f3ced1feba99bb2930 Mon Sep 17 00:00:00 2001 From: American_Jesus Date: Fri, 19 Dec 2025 10:55:29 +0000 Subject: [PATCH 11/45] Translated using Weblate (Portuguese) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt/ --- app/src/main/res/values-pt/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5abc23a0..346cd2c6 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -374,4 +374,5 @@ Insira o endereço do servidor Nenhum servidor encontrado Estilo de fundo + Termina às %1$s From 0ccdf391dea124f600bb52027efee127f1b871fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 19 Dec 2025 12:15:46 +0000 Subject: [PATCH 12/45] Translated using Weblate (Estonian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/ --- app/src/main/res/values-et/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 59aa1f7b..4809541d 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -358,4 +358,5 @@ Sisesta serveri aadress Ühtegi serverit ei leidu Tausta dekoratsioon + Lõppeb kell %1$s From 3cd26759f4e32423c706b3d557c8d13c9ffc23e2 Mon Sep 17 00:00:00 2001 From: arcker95 Date: Thu, 18 Dec 2025 23:29:40 +0000 Subject: [PATCH 13/45] Translated using Weblate (Italian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 61a7b11b..c8837cdf 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -374,4 +374,5 @@ Inserisci indirizzo server Nessun server trovato Stile backdrop + Termina alle %1$s From 8ee6b9d8b59aaad62865c6a33ee713a572edab1a Mon Sep 17 00:00:00 2001 From: lazigeri Date: Fri, 19 Dec 2025 01:09:43 +0000 Subject: [PATCH 14/45] Translated using Weblate (Hungarian) Currently translated at 99.3% (313 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 147 ++++++++++++++++--------- 1 file changed, 98 insertions(+), 49 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 5130a5a3..c5d24325 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -10,12 +10,12 @@ Született Mégse Fejezetek - Válassz %1$s + Válasszon %1$s Kép gyorsítótár törlése Gyűjtemény Gyűjtemények Közösségi értékelés - Hiba történt! Nyomd meg a gombot, hogy elküldd a naplófájlokat a szervernek. + Hiba történt! Nyomja meg a gombot, hogy elküldje a naplófájlokat a szervernek. Megerősít Kritikusi értékelés %.1f másodperc @@ -28,7 +28,7 @@ Letöltés folyamatban… Engedélyezve - Add meg a szerver IP-t vagy URL-t + Adja meg a szerver IP-t vagy URL-t Epizódok Hiba a gyűjtemény betöltése során: %1$s Külső @@ -38,25 +38,25 @@ Sorozatfelvétel leállítása Reklám Megtekintés folytatása - Halott + Elhunyt Méret - Kényszerített + Kiegészítő Műfajok Ugrás a sorozathoz Ugrás Lejátszási gombok elrejtése - Hibajavítási infó elrejtése + Hibakeresési infó elrejtése Elrejt Kezdőlap Azonnal - Intro + Intró #ABCDEFGHIJKLMNOPQRSTUVWXYZ Könyvtár Lincenc információk Élő TV Betöltés… - Megjelölöd az egész sorozatot megnézettként? - Megjelölöd az egész sorozatot megnézetlenként? + Megjelöli az egész sorozatot megnézettként? + Megjelöli az egész sorozatot megnézetlenként? Jelölés megnézetlenként Jelölés megnézettként Max bitráta @@ -70,7 +70,7 @@ Nincs időzített felvétel Nincs elérhető frissítés Nincs - Outro + Stáblista Elérési út Emberek Lejátszások száma @@ -89,7 +89,7 @@ Nemrég rögzített Nemrég kiadott Ajánlott - Program felvétele + Műsor felvétele Sorozat felvétele Törlés a kedvencekből Újraindítás @@ -110,7 +110,7 @@ Lejátszás dátuma Megjelenés dátuma Név - Random + Véletlenszerűen Stúdiók Beküld A letöltés sok időt vesz igénybe, előfordulhat, hogy újra kell indítanod a lejátszást @@ -134,14 +134,14 @@ Ismeretlen Frissítések Verzió - Képarány + Kép méretezése Videó Videók Nézd élőben %1$d éves Lejátszás átalakítással Aktuális idő mutatása - Sorrend epízód vetítése alapján + Sorrend epizód vetítése alapján Új lejátszási lista létrehozása Hozzáadás lejátszási listához Szüneteltetés egy klikkel @@ -155,7 +155,7 @@ Extrák Egyéb - + Egyebek Színfalak mögött @@ -166,36 +166,36 @@ - Téma videók - + Téma videó + Téma videók - Klippek - + Klip + Klipek - Törölt jelenetek - + Törölt jelenet + Törölt jelenetek - Interjúk - + Interjú + Interjúk - Jelenetek - + Jelenet + Jelenetek - Minták - + Minta + Minták - Rövidfilmek - + Rövidfilm + Rövidfilmek - Rövidek - + Rövid + Rövidek %s letöltés @@ -213,30 +213,30 @@ %d másodperc %d másodpercek - Véget ér ekkor: %1$s + Véget ér: %1$s Nem található szerver Visszatekintés - Média Információ + Média információ A készülék támogatja az AC3/Dolby Digital formátumot - Haladó Beállítások - Haladó Felhasználói Felület - Alkalmazás Témája + Haladó beállítások + Haladó felhasználói felület beállítások + Alkalmazás témája Frissítések automatikus keresése Késleltetés a következő elem lejátszása előtt Következő elem automatikus lejátszása Frissítések keresése - Csak a sorozatokra vonatkzik + Csak a sorozatokra vonatkozik ASS feliratok közvetlen lejátszása PGS feliratok közvetlen lejátszása Mindig konvertálja sztereóvá Használja az FFmpeg dekóder modulját - A tartalom alapértelmezett mérete + Tartalom alapértelmezett mérete Frissítés telepítése Telepített verzió - Elemek maximális száma a kezdőlapi sorokon - Válaszd ki az alapértelmezett elemeket, amelyek megjelennek, a többi el lesz rejtve + Maximális elemek soronként a kezdőlapon + Válassza ki az alapértelmezett elemeket, amelyek megjelennek, a többi el lesz rejtve Navigációs Fiók elemeinek testreszabása - Klikkelj az oldalak közti váltáshoz + Kattintson az oldalak közti váltáshoz Automatikus leállítás inaktivitás esetén Főcímdal lejátszása Kiválasztott lapok megjegyzése @@ -249,12 +249,12 @@ Beállítások Visszaugrás a lejátszás folytatásakor Visszaugrás - Reklámok átugrása + Reklám-átugrás viselkedés Előreugrás - Intró átugrása - Befejezés átugrása - Epizódelőzetesek átugrása - Összefoglaló átugrása + Intró-átugrás viselkedés + Stáblista-átugrás viselkedés + Előzetes-átugrás viselkedés + Összefoglalás-átugrás viselkedés Frissítés érhető el Az alkalmazás frissítéseinek ellenőrzésére használt URL Frissítés URL @@ -270,13 +270,13 @@ Visszaállítás Félkövér betűk MPV: hardveres dekódolás használata - Tiltsd le, ha összeomlást tapasztalsz + Tiltsa le, ha összeomlást tapasztal MPV beállítások ExoPlayer beállítások Szegmens átugrása Reklám átugrása Előzetes átugrása - Összefoglaló átugrása + Összefoglalás átugrása Stáblista átugrása Intró átugrása Lejátszott @@ -288,7 +288,7 @@ Dolby Atmos MPV: gpu-next használata mpv.conf szerkesztése - Változások elvetése? + Elveti a változtatásokat? Margók Részletes naplózás Adja meg a PIN-kódot @@ -308,4 +308,53 @@ Kép típusa Vendégszereplők Szegély mérete + Tartalomhoz igazodó képfrissítési gyakoriság + Automatikus + Felhasználónév/jelszó használata + Bejelentkezés + Általános + Konténer + Cím + Kodek + Profil + Szint + Felbontás + Anamorfikus + Váltottsoros + Képfrissítés + Színmélység + Feketeszint tartomány + Feketeszint tartomány típusa + Színtér + Színátviteli jellemző + Alapszínek + Pixelformátum + Referencia-képkockák + NAL + Nyelv + Elrendezés + Csatornák + Mintavételezés + AVC + Igen + Nem + SDR + HDR + HDR10 + HDR10+ + HLG + bit + Hz + Címek mutatása + Ismétlés + A kedvenc csatornákat mutassa először + Legutóbb nézett csatornák előre sorolása + Műsorok kategória szerinti színezése + Felirat késleltetése + Feliratháttér stílusa + Adja meg a szerver címét + + Lejátszás backend + Kép gyorsítótár méret (MB) + From aad81f85c9c3ba0548cf2f64fa124248308cb9cd Mon Sep 17 00:00:00 2001 From: adambibor Date: Fri, 19 Dec 2025 00:06:26 +0000 Subject: [PATCH 15/45] Translated using Weblate (Hungarian) Currently translated at 99.3% (313 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index c5d24325..066ee60a 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -215,7 +215,7 @@ Véget ér: %1$s Nem található szerver - Visszatekintés + Összefoglalás Média információ A készülék támogatja az AC3/Dolby Digital formátumot Haladó beállítások From a2535b098d583dc08d2400d12cc3743519de75ec Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Fri, 19 Dec 2025 00:23:22 +0000 Subject: [PATCH 16/45] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 674948ad..e8223887 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -342,4 +342,5 @@ 输入服务器地址 未找到服务器 背景幕样式 + 结束于 %1$s From 6cb320c43fa30a2d94e88662f462a225f5f3e0b4 Mon Sep 17 00:00:00 2001 From: RabSsS Date: Fri, 19 Dec 2025 16:24:35 +0000 Subject: [PATCH 17/45] Translated using Weblate (French) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/fr/ --- app/src/main/res/values-fr/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index ba3fb676..539ca50c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -370,4 +370,9 @@ Images de référence Trier les chaînes par date de visionnage Classer les programmes par code couleur + Terminera à %1$s + Entrez l\'adresse du serveur + Aucun serveur trouvé + Décalage des sous-titres + Style d\'arrière-plan From 6f5e0d82d141a2f0e4da1901c61a3d7049a26b58 Mon Sep 17 00:00:00 2001 From: OmriKumri Date: Sat, 20 Dec 2025 16:54:01 +0000 Subject: [PATCH 18/45] Translated using Weblate (Hebrew) Currently translated at 6.6% (21 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/he/ --- app/src/main/res/values-iw/strings.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 045e125f..cfd44228 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -1,3 +1,20 @@ + אודות + הקלטות פעילות + הוספה למועדפים + הוסף שרת + הוסף משתמש + שמע + מקום לידה + קצב נתונים + בטל הקלטה + בטל הקלטת סדרה + בטל + פרקים + נקה זיכרון מטמון + אוסף + אוספים + מסחרי + דירוג קהילתי From dda8baa46d58393eaa2f3020ed00cb740ce3effa Mon Sep 17 00:00:00 2001 From: OmriKumri Date: Sat, 20 Dec 2025 17:03:23 +0000 Subject: [PATCH 19/45] Translated using Weblate (Hebrew) Currently translated at 10.7% (34 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/he/ --- app/src/main/res/values-iw/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index cfd44228..51d5978a 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -17,4 +17,17 @@ אוספים מסחרי דירוג קהילתי + אירעה שגיאה! לחץ על הכפתור על מנת לשלוח יומני אירועים לשרת שלך. + לאשר + המשך צפייה + דירוג מבקרים + %.1f שניות + ברירת מחדל + מחק + מת + בבימויו של %1$s + במאי + מושבת + נולד + בחר %1$s From 6af820440205b306587cc7c0e50bf44813d1f0be Mon Sep 17 00:00:00 2001 From: lazigeri Date: Sat, 20 Dec 2025 18:53:44 +0000 Subject: [PATCH 20/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 066ee60a..ef00d3b1 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1,7 +1,7 @@ Névjegy - Kedvenc + Hozzáadás kedvencekhez Szerver hozzáadása Felhasználó hozzáadása Audio @@ -87,7 +87,7 @@ Nemrég hozzáadva: %1$s Nemrég hozzáadott Nemrég rögzített - Nemrég kiadott + Nemrég megjelent Ajánlott Műsor felvétele Sorozat felvétele @@ -113,7 +113,7 @@ Véletlenszerűen Stúdiók Beküld - A letöltés sok időt vesz igénybe, előfordulhat, hogy újra kell indítanod a lejátszást + A letöltés sok időt vesz igénybe, előfordulhat, hogy újra kell indítani a lejátszást Felirat Feliratok Ajánlások @@ -137,7 +137,7 @@ Kép méretezése Videó Videók - Nézd élőben + Nézze élőben %1$d éves Lejátszás átalakítással Aktuális idő mutatása @@ -145,12 +145,12 @@ Új lejátszási lista létrehozása Hozzáadás lejátszási listához Szüneteltetés egy klikkel - Nyomd a D-Pad közepét megállításhoz/lejátszáshoz + Nyomja le a D-Pad közepét megállításhoz/lejátszáshoz Dőlt betűk Betűtípus Háttérszín Sikeres - Szülői besorolás + Korhatár-besorolás Műsoridő Extrák @@ -162,8 +162,8 @@ - Téma zenék - + Főcímdal + Főcímdalok Téma videó @@ -357,4 +357,6 @@ Lejátszás backend Kép gyorsítótár méret (MB) + Navigációs menü oldalainak váltása fókusznál + Lejátszás felülbírálatai From 295faf51aa6eb16cd52987f66046cbc308c8469c Mon Sep 17 00:00:00 2001 From: texxlan Date: Mon, 22 Dec 2025 00:00:51 +0000 Subject: [PATCH 21/45] Translated using Weblate (German) Currently translated at 77.1% (243 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/ --- app/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1d4610a6..f8c4900f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -286,4 +286,5 @@ Entfernen Dolby Vision Dolby Atmos + Endet um %1$s From 5c9ca3b24abf74d3dca4bf4aa96a4b53edc4de5f Mon Sep 17 00:00:00 2001 From: jab3 Date: Sun, 21 Dec 2025 14:37:58 +0000 Subject: [PATCH 22/45] Translated using Weblate (Spanish) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 78 +++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f1ca329f..b58bd8f5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,6 +1,6 @@ - Favorito + Añadir a Favoritos Agregar servidor Agregar usuario Audio @@ -170,8 +170,8 @@ Canciones principales - - + + Vídeos temáticos @@ -303,4 +303,76 @@ Fuente Fondo Clasificación parental + Termina en %1$s + Introduzca la dirección del servidor + No se encontraron servidores + Información multimedia + MPV: Usar gpu-next + Editar mpv.conf + ¿Descartar los cambios? + Margen + Registro detallado + Introducir PIN + Iniciar sesión automáticamente + Iniciar sesión a través del servidor + Pulsa el centro para confirmar + Requerir PIN para el perfil + Confirmar PIN + Incorrecto + El PIN debe tener 4 teclas o más + Se eliminará el PIN + Tamaño de la caché de disco de imágenes (MB) + Opciones de visualización + Columnas + Espaciado + Relación de aspecto + Mostrar detalles + Tipo de imagen + + Estrellas invitadas + Tamaño del borde + Cambio de frecuencia de actualización + Automático + Usar nombre de usuario/contraseña + Iniciar sesión + General + Contenedor + Título + Codec + Perfil + Nivel + Resolución + Anamórfico + Entrelazado + Frecuencia de fotogramas + Profundidad de bits + Rango de vídeo + Tipo de rango de vídeo + Espacio de color + Transferencia de color + Primarias de color + Formato de píxel + Fotogramas de referencia + NAL + Idioma + Distribución + Canales + Frecuencia de muestreo + AVC + + No + SDR + HDR + HDR10 + HDR10+ + HLG + bit + Hz + Mostrar títulos + Repetir + Mostrar primero los canales favoritos + Ordenar los canales por vistos recientemente + Programas codificados por colores + Retardo de subtítulos + Estilo del fondo From a2e72ddf45516fd6840f198e05e6b186d2a59db7 Mon Sep 17 00:00:00 2001 From: lazigeri Date: Sun, 21 Dec 2025 23:59:48 +0000 Subject: [PATCH 23/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index ef00d3b1..ef120937 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -21,7 +21,7 @@ %.1f másodperc Alapértelmezett Törlés - Rendezte %1$s + Rendezte: %1$s Rendező Letiltva Felfedezett szerverek @@ -72,7 +72,7 @@ Nincs Stáblista Elérési út - Emberek + Stáb Lejátszások száma Lejátszás innen Lejátszás From 8f923c187f462863bf92c7566ffa4620652b7acd Mon Sep 17 00:00:00 2001 From: texxlan Date: Mon, 22 Dec 2025 00:12:05 +0000 Subject: [PATCH 24/45] Translated using Weblate (German) Currently translated at 87.9% (277 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/ --- app/src/main/res/values-de/strings.xml | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f8c4900f..23dca91e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -287,4 +287,38 @@ Dolby Vision Dolby Atmos Endet um %1$s + Server Adresse eingeben + Keine Server gefunden + Medieninformation + Abgespielt + MPV: Benutze gpu-next + mpv.conf bearbeiten + Änderungen verwerfen? + Abstand + PIN Eingeben + Automatisch anmelden + Profil benötigt PIN + PIN bestätigen + Falsch + PIN muss 4 Zeichen oder länger sein + PIN wird entfernt + Anzeigeoptionen + Spalten + Abstände + Seitenverhältnis + Details anzeigen + Bild Typ + Automatisch + Verwende Benutzername/Passwort + Anmelden + Titel + Profil + Auflösung + Bildrate + Sprache + Kanäle + Ja + Nein + Titel anzeigen + Wiederholen From f240498aaa5358c93546f073c3cfa6db944ea2ef Mon Sep 17 00:00:00 2001 From: lazigeri Date: Mon, 22 Dec 2025 00:12:49 +0000 Subject: [PATCH 25/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index ef120937..4af5a5d2 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -72,7 +72,7 @@ Nincs Stáblista Elérési út - Stáb + Stábtagok Lejátszások száma Lejátszás innen Lejátszás @@ -105,7 +105,7 @@ Megmutat Véletlenszerű Átugrás - Hozzáadva + Hozzáadás dátuma Epizód hozzáadásának dátuma Lejátszás dátuma Megjelenés dátuma @@ -151,7 +151,7 @@ Háttérszín Sikeres Korhatár-besorolás - Műsoridő + Hossz Extrák Egyéb @@ -203,7 +203,7 @@ %d óra - %d órák + %d óra %d elem @@ -211,7 +211,7 @@ %d másodperc - %d másodpercek + %d másodperc Véget ér: %1$s Nem található szerver @@ -289,7 +289,7 @@ MPV: gpu-next használata mpv.conf szerkesztése Elveti a változtatásokat? - Margók + Függőleges eltolás Részletes naplózás Adja meg a PIN-kódot Automatikus bejelentkezés From c74c40dfd8d8af639a77f8faf59b28fcbfe006ab Mon Sep 17 00:00:00 2001 From: oyvhov Date: Mon, 22 Dec 2025 05:13:58 +0000 Subject: [PATCH 26/45] Translated using Weblate (Norwegian Nynorsk) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/nn/ --- app/src/main/res/values-nn/strings.xml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index a2fbd5b8..f08dadd2 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -125,7 +125,7 @@ Trailer Trailerar - + DVR-plan Programoversikt @@ -158,47 +158,47 @@ Ekstra Anna - + Bak kulissane - + Temasongar - + Temavideoar - + Klipp - + Sletta sener - + Intervju - + Scener - + Smakebitar - + Bakomfilmar - + Kortfilmar - + Avanserte innstillingar Avansert grensesnitt @@ -358,4 +358,5 @@ %d sekund Vel standardelementa som skal visast, andre vert skjulde + Sluttar %1$s From 5a8dc239894a7b7e3d0191f3b888bab794216c47 Mon Sep 17 00:00:00 2001 From: opakholis Date: Tue, 23 Dec 2025 04:11:44 +0000 Subject: [PATCH 27/45] Translated using Weblate (Indonesian) Currently translated at 97.7% (308 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/id/ --- app/src/main/res/values-in/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index fc1f6866..456bee92 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -332,4 +332,8 @@ Resolusi Sampel rate Jarak + Berakhir pada %1$s + Masukkan alamat server + Server tidak ditemukan + Penundaan subtitel From 1c92934b331531c917975adab9519874656e59b2 Mon Sep 17 00:00:00 2001 From: butterflyoffire Date: Wed, 24 Dec 2025 08:59:30 +0000 Subject: [PATCH 28/45] Added translation using Weblate (Kabyle) --- app/src/main/res/values-kab/strings.xml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/src/main/res/values-kab/strings.xml diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml new file mode 100644 index 00000000..55344e51 --- /dev/null +++ b/app/src/main/res/values-kab/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file From ab9dbed0225d729a30ce0044907d491e8fb7909e Mon Sep 17 00:00:00 2001 From: butterflyoffire Date: Wed, 24 Dec 2025 09:06:36 +0000 Subject: [PATCH 29/45] Translated using Weblate (Kabyle) Currently translated at 14.9% (47 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/kab/ --- app/src/main/res/values-kab/strings.xml | 45 ++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 55344e51..43c74c5a 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -1,3 +1,46 @@ - \ No newline at end of file + Ɣef + Imesli + Adig n tlalit + Aktum + Semmet + Ixfawen + Tagrumma + Tigrummiwin + Serggeg + Amezwer + Kkes + Yemmut + Ffer-it + Tazwara + #ABCDEFGHIJKLMNOPQRSTUVWXYZ + Tamkarḍit + Ales asekker + Kemmel + Sekles + Nadi + Yettnadi… + Sken-d + Isem + Agacuṛan + Lqem + Tavidyut + Tividyutin + Tasefsit + Agilal + Yedda + Aseggas + Awurman + Tuqqna + Amatu + Amagbar + Azwel + Akudak + Amaɣnu + Aswir + HDR10 + HDR10+ + HLG + Fren %1$s + From f463e707cc7181ee993f48115c4309e24eb6d41f Mon Sep 17 00:00:00 2001 From: jab3 Date: Fri, 26 Dec 2025 11:34:32 +0000 Subject: [PATCH 30/45] Translated using Weblate (Spanish) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b58bd8f5..ce48243d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,8 +1,8 @@ Añadir a Favoritos - Agregar servidor - Agregar usuario + Añadir servidor + Añadir usuario Audio Lugar de nacimiento Tasa de bits From b9a4bbd3f3a86aceef9f6ba0b62b2a3a3395cfdf Mon Sep 17 00:00:00 2001 From: SimonHung Date: Fri, 26 Dec 2025 05:06:20 +0000 Subject: [PATCH 31/45] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 0130d2aa..ce437136 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -306,4 +306,41 @@ 節目顏色標記 優先顯示最愛頻道 按最近觀看的頻道排序 + 結束於 %1$s + 一般資訊 + 封裝格式 + 標題 + 編碼 + 解析度 + 變形寬銀幕 + 等級 + 編碼設定檔 + 語言 + + + SDR + HDR + HDR10 + HDR10+ + HLG + bit + Hz + 重複 + 隔行掃描 + 參考影格數 + 影格率 + NAL + 位元深度 + AVC + 聲道配置 + 聲道數 + 動態範圍 + 動態範圍類型 + 色彩空間 + 色彩轉換 + 色彩原色 + 像素格式 + 取樣率 + 進度條跳轉值 + 播放覆蓋設定 From 716d81f0d27cf2491cedaa72eff69e10a67a81c8 Mon Sep 17 00:00:00 2001 From: lazigeri Date: Fri, 26 Dec 2025 22:03:38 +0000 Subject: [PATCH 32/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 4af5a5d2..2d4fe7b4 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -78,7 +78,7 @@ Lejátszás Lejátszás hibakeresési adatainak mutatása Lejátszási sebesség - Visszajátszás + Lejátszás Lejátszási lista Lejátszási listák Előnézet From 0e0e0b12eb675eb6bee4250a6ae6befa6f4b7f50 Mon Sep 17 00:00:00 2001 From: lazigeri Date: Sat, 27 Dec 2025 20:57:06 +0000 Subject: [PATCH 33/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 2d4fe7b4..afb72545 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -4,7 +4,7 @@ Hozzáadás kedvencekhez Szerver hozzáadása Felhasználó hozzáadása - Audio + Hang Születési hely Bitráta Született From d2e8440a0375fb6012b6defe98912def19f61439 Mon Sep 17 00:00:00 2001 From: SimonHung Date: Sun, 28 Dec 2025 07:29:24 +0000 Subject: [PATCH 34/45] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ce437136..efa7096c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -183,8 +183,8 @@ #ABCDEFGHIJKLMNOPQRSTUVWXYZ 授權資訊 電視直播 - 標記整部電視劇為已觀看? - 標記整部電視劇為未觀看? + 標記整部劇為已觀看? + 標記整部劇為未觀看? 標記為未觀看 標記為已觀看 最大位元率 @@ -307,7 +307,7 @@ 優先顯示最愛頻道 按最近觀看的頻道排序 結束於 %1$s - 一般資訊 + 一般 封裝格式 標題 編碼 From bad567f2bb8285b90bc37d7eb24182924b2d6d99 Mon Sep 17 00:00:00 2001 From: lazigeri Date: Sun, 28 Dec 2025 18:45:45 +0000 Subject: [PATCH 35/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 54 +++++++++++++------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index afb72545..35c0d839 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -10,8 +10,8 @@ Született Mégse Fejezetek - Válasszon %1$s - Kép gyorsítótár törlése + %1$s kiválasztása + Képgyorsítótár törlése Gyűjtemény Gyűjtemények Közösségi értékelés @@ -30,7 +30,7 @@ Engedélyezve Adja meg a szerver IP-t vagy URL-t Epizódok - Hiba a gyűjtemény betöltése során: %1$s + Hiba a következő gyűjtemény betöltése során: %1$s Külső Kedvencek Aktív felvételek @@ -43,7 +43,7 @@ Kiegészítő Műfajok Ugrás a sorozathoz - Ugrás + Ugrás ide: Lejátszási gombok elrejtése Hibakeresési infó elrejtése Elrejt @@ -59,7 +59,7 @@ Megjelöli az egész sorozatot megnézetlenként? Jelölés megnézetlenként Jelölés megnézettként - Max bitráta + Maximális bitráta Hasonlóak Továbbiak Filmek @@ -100,7 +100,7 @@ Keresés… Szerver kiválasztása Felhasználó kiválasztása - Hibakeresési info megjelenítése + Hibakeresési infó megjelenítése Következő mutatása Megmutat Véletlenszerű @@ -117,9 +117,9 @@ Felirat Feliratok Ajánlások - Váltás szerverek között + Váltás másik szerverre Váltás - Legjobbra értékelt nem megtekintett + Legjobbra értékelt, nem megnézett Előzetes Előzetesek @@ -130,7 +130,7 @@ Évad Évadok Sorozatok - Felület + Kezelőfelület Ismeretlen Frissítések Verzió @@ -147,8 +147,8 @@ Szüneteltetés egy klikkel Nyomja le a D-Pad közepét megállításhoz/lejátszáshoz Dőlt betűk - Betűtípus - Háttérszín + Betűk stílusa + Háttér Sikeres Korhatár-besorolás Hossz @@ -199,7 +199,7 @@ %s letöltés - %s letöltések + %s letöltés %d óra @@ -207,35 +207,35 @@ %d elem - %d elemek + %d elem %d másodperc %d másodperc - Véget ér: %1$s + %1$s-kor ér véget Nem található szerver - Összefoglalás - Média információ + Összefoglaló + Médiainformáció A készülék támogatja az AC3/Dolby Digital formátumot Haladó beállítások - Haladó felhasználói felület beállítások + Haladó kezelőfelület-beállítások Alkalmazás témája Frissítések automatikus keresése Késleltetés a következő elem lejátszása előtt Következő elem automatikus lejátszása Frissítések keresése - Csak a sorozatokra vonatkozik + Csak sorozatok esetén ASS feliratok közvetlen lejátszása PGS feliratok közvetlen lejátszása Mindig konvertálja sztereóvá - Használja az FFmpeg dekóder modulját - Tartalom alapértelmezett mérete + FFmpeg dekóder modul használata + Tartalom alapértelmezett méretezése Frissítés telepítése Telepített verzió Maximális elemek soronként a kezdőlapon Válassza ki az alapértelmezett elemeket, amelyek megjelennek, a többi el lesz rejtve - Navigációs Fiók elemeinek testreszabása + Navigációs menü elemeinek testreszabása Kattintson az oldalak közti váltáshoz Automatikus leállítás inaktivitás esetén Főcímdal lejátszása @@ -254,7 +254,7 @@ Intró-átugrás viselkedés Stáblista-átugrás viselkedés Előzetes-átugrás viselkedés - Összefoglalás-átugrás viselkedés + Összefoglaló-átugrás viselkedés Frissítés érhető el Az alkalmazás frissítéseinek ellenőrzésére használt URL Frissítés URL @@ -264,7 +264,7 @@ Szegély stílusa Szegély színe Háttér áttetszősége - Háttér stílusa + Háttér típusa Felirat stílusa Háttér színe Visszaállítás @@ -276,7 +276,7 @@ Szegmens átugrása Reklám átugrása Előzetes átugrása - Összefoglalás átugrása + Összefoglaló átugrása Stáblista átugrása Intró átugrása Lejátszott @@ -300,9 +300,9 @@ Helytelen A PIN-kódnak legalább 4 karakterből kell állnia PIN-kód eltávolítása - Nézet beállítások + Nézetbeállítások Oszlopok - Helykihagyás + Helyköz Képarány Részletek mutatása Kép típusa @@ -351,7 +351,7 @@ Legutóbb nézett csatornák előre sorolása Műsorok kategória szerinti színezése Felirat késleltetése - Feliratháttér stílusa + Háttér stílusa Adja meg a szerver címét Lejátszás backend From b62cc84bf90f2ab4a3cfab0876f7e44da8b15fc9 Mon Sep 17 00:00:00 2001 From: lazigeri Date: Sun, 28 Dec 2025 22:27:00 +0000 Subject: [PATCH 36/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (315 of 315 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hu/ --- app/src/main/res/values-hu/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 35c0d839..e9c73c87 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -356,7 +356,7 @@ Lejátszás backend Kép gyorsítótár méret (MB) - + Navigációs menü oldalainak váltása fókusznál Lejátszás felülbírálatai From 2b68b91d9e3535c76475bbafd7f57a5949f70100 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Tue, 30 Dec 2025 13:49:20 -0500 Subject: [PATCH 37/45] Fix invalid string characters --- app/src/main/res/values-nn/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index f08dadd2..b4826c5b 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -208,7 +208,7 @@ Spel neste automatisk Sjekk etter oppdateringar Gjeld berre TV-seriar - + Slå saman \'Hald fram med å sjå\' og \'Neste\' Direkteavspeling av ASS-undertekstar Direkteavspeling av PGS-undertekstar Alltid nedmiks til stereo From 3c11d4ba12ecef3706c4b9c45eaef72a6774b2f9 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:37:32 -0500 Subject: [PATCH 38/45] Fix some issues with restoring focus between pages (#613) ## Description Yet more focus issue fixes: - Restore focus when going back to home page from a later row - Don't jump to episode row when going back to series overview (such as from a person page) This PR also changes how backgrounding the app during playback works. Now the only goes back to the previous page when the app comes back to the foreground. This helps with focusing back on the page properly. There's also a bit more clean up performed. --- .../services/PlaybackLifecycleObserver.kt | 7 ++--- .../damontecres/wholphin/ui/Extensions.kt | 10 +++---- .../ui/detail/series/SeriesOverview.kt | 16 ++++++----- .../ui/detail/series/SeriesOverviewContent.kt | 2 -- .../damontecres/wholphin/ui/main/HomePage.kt | 4 ++- .../wholphin/ui/playback/PlaybackViewModel.kt | 27 ++++++++++--------- 6 files changed, 35 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt b/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt index 86906280..3c32a497 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/PlaybackLifecycleObserver.kt @@ -20,6 +20,10 @@ class PlaybackLifecycleObserver private var wasPlaying: Boolean? = null override fun onStart(owner: LifecycleOwner) { + val lastDest = navigationManager.backStack.lastOrNull() + if (lastDest is Destination.Playback || lastDest is Destination.PlaybackList) { + navigationManager.goBack() + } wasPlaying = null } @@ -40,9 +44,6 @@ class PlaybackLifecycleObserver } override fun onStop(owner: LifecycleOwner) { - if (navigationManager.backStack.lastOrNull() is Destination.Playback) { - navigationManager.goBack() - } themeSongPlayer.stop() } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt index 4eba1b32..31754ef9 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt @@ -28,9 +28,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.compose.LifecycleEventEffect import androidx.media3.common.Player import coil3.request.ErrorResult import com.github.damontecres.wholphin.data.model.BaseItem @@ -182,10 +180,10 @@ fun RequestOrRestoreFocus( debugKey?.let { Timber.v("RequestOrRestoreFocus: %s", it) } focusRequester.tryRequestFocus() } - LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { - debugKey?.let { Timber.v("RequestOrRestoreFocus onResume: %s", it) } - focusRequester.tryRequestFocus() - } +// LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { +// debugKey?.let { Timber.v("RequestOrRestoreFocus onResume: %s", it) } +// focusRequester.tryRequestFocus() +// } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt index 9377aa1d..b5d4ea4b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.map import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.preferences.UserPreferences +import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.ErrorMessage @@ -37,7 +38,6 @@ import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.rememberInt import com.github.damontecres.wholphin.ui.seasonEpisode -import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.util.LoadingState import kotlinx.coroutines.flow.update import kotlinx.serialization.Serializable @@ -158,13 +158,15 @@ fun SeriesOverview( LoadingState.Success -> { series?.let { series -> - LaunchedEffect(Unit) { + RequestOrRestoreFocus( when (rowFocused) { - EPISODE_ROW -> episodeRowFocusRequester.tryRequestFocus() - CAST_AND_CREW_ROW -> castCrewRowFocusRequester.tryRequestFocus() - GUEST_STAR_ROW -> guestStarRowFocusRequester.tryRequestFocus() - } - } + EPISODE_ROW -> episodeRowFocusRequester + CAST_AND_CREW_ROW -> castCrewRowFocusRequester + GUEST_STAR_ROW -> guestStarRowFocusRequester + else -> episodeRowFocusRequester + }, + "series_overview", + ) LifecycleStartEffect(destination.itemId) { viewModel.maybePlayThemeSong( destination.itemId, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt index 75d8344b..1495285d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt @@ -49,7 +49,6 @@ import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.Person import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.ui.AspectRatios -import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus import com.github.damontecres.wholphin.ui.cards.BannerCard import com.github.damontecres.wholphin.ui.cards.PersonRow import com.github.damontecres.wholphin.ui.components.ErrorMessage @@ -182,7 +181,6 @@ fun SeriesOverviewContent( is EpisodeList.Success -> { val state = rememberLazyListState(position.episodeRowIndex) - RequestOrRestoreFocus(firstItemFocusRequester) LazyRow( state = state, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt index 31a4ecf0..6cc69dcd 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt @@ -234,7 +234,7 @@ fun HomePageContent( val listState = rememberLazyListState() val rowFocusRequesters = remember(homeRows.size) { List(homeRows.size) { FocusRequester() } } - var focused by remember { mutableStateOf(false) } + var focused by rememberSaveable { mutableStateOf(false) } LaunchedEffect(homeRows) { if (!focused) { homeRows @@ -246,6 +246,8 @@ fun HomePageContent( listState.animateScrollToItem(position.row) focused = true } + } else { + rowFocusRequesters.getOrNull(position.row)?.tryRequestFocus() } } LaunchedEffect(position) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index 39cee993..064434c4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -157,6 +157,7 @@ class PlaybackViewModel internal lateinit var item: BaseItem internal var forceTranscoding: Boolean = false private var activityListener: TrackActivityPlaybackListener? = null + private val jobs = mutableListOf() val nextUp = MutableLiveData() private var isPlaylist = false @@ -166,20 +167,22 @@ class PlaybackViewModel val subtitleSearchLanguage = MutableLiveData(Locale.current.language) init { - viewModelScope.launch(ExceptionHandler()) { controllerViewState.observe() } - player.addListener(this) - (player as? ExoPlayer)?.addAnalyticsListener(this) - addCloseable { player.removeListener(this@PlaybackViewModel) } - addCloseable { (player as? ExoPlayer)?.removeAnalyticsListener(this@PlaybackViewModel) } addCloseable { + player.removeListener(this@PlaybackViewModel) + (player as? ExoPlayer)?.removeAnalyticsListener(this@PlaybackViewModel) + this@PlaybackViewModel.activityListener?.let { it.release() player.removeListener(it) } + jobs.forEach { it.cancel() } + player.release() } - addCloseable { player.release() } - subscribe() - listenForTranscodeReason() + viewModelScope.launch(ExceptionHandler()) { controllerViewState.observe() } + player.addListener(this) + (player as? ExoPlayer)?.addAnalyticsListener(this) + jobs.add(subscribe()) + jobs.add(listenForTranscodeReason()) } /** @@ -874,7 +877,7 @@ class PlaybackViewModel } } - private fun listenForTranscodeReason() { + private fun listenForTranscodeReason(): Job = viewModelScope.launchIO { currentPlayback.collectLatest { if (it != null) { @@ -905,7 +908,6 @@ class PlaybackViewModel } } } - } private var lastInteractionDate: Date = Date() @@ -1026,11 +1028,13 @@ class PlaybackViewModel } fun release() { + Timber.v("release") activityListener?.release() player.release() + activityListener = null } - fun subscribe() { + fun subscribe(): Job = api.webSocket .subscribe() .onEach { message -> @@ -1085,7 +1089,6 @@ class PlaybackViewModel } } }.launchIn(viewModelScope) - } /** * Atomically update [currentMediaInfo] From 977db0474ab7494ae768965565677ef935dc7640 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Thu, 1 Jan 2026 15:07:32 -0500 Subject: [PATCH 39/45] Small bug fixes (#614) ## Description Fixes the screen showing endless loading when toggling on automatic sign in Ports a bug fix to the device profile from the official app (https://github.com/jellyfin/jellyfin-androidtv/pull/5268) --- .../com/github/damontecres/wholphin/MainActivity.kt | 12 ++++++------ .../wholphin/util/profile/DeviceProfileUtils.kt | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index 4e6e22b0..b55b6dba 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -23,8 +23,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.unit.dp import androidx.datastore.core.DataStore +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModel -import androidx.lifecycle.compose.LifecycleStartEffect +import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator @@ -224,13 +225,12 @@ class MainActivity : AppCompatActivity() { var showContent by remember { mutableStateOf(true) } - if (!preferences.appPreferences.signInAutomatically) { - LifecycleStartEffect(Unit) { - onStopOrDispose { - showContent = false - } + LifecycleEventEffect(Lifecycle.Event.ON_STOP) { + if (!preferences.appPreferences.signInAutomatically) { + showContent = false } } + if (showContent) { ApplicationContent( user = current.user, diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt index a143d860..b7d28db1 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt @@ -210,7 +210,7 @@ fun createDeviceProfile( "main", "baseline", "constrained baseline", - if (supportsAVCHigh10) "main 10" else null, + if (supportsAVCHigh10) "high 10" else null, ) } } From 09585780827c7418b131d38da0eb7ea7983db442 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Thu, 1 Jan 2026 22:19:36 -0500 Subject: [PATCH 40/45] Fixes websocket subscription, date invalidation, & theme music (#616) ## Description Makes sure the `ServerEventListener` and `DatePlayedInvalidationService` are injected so that they run. This was broken in #538. Also fixes the a delayed stopping of theme music Also fixes for detecting a non-seamless refresh rate switch ### Related issues Fixes #615 Fixes #568 --- .../com/github/damontecres/wholphin/MainActivity.kt | 10 ++++++++++ .../wholphin/services/RefreshRateService.kt | 10 +++++----- .../damontecres/wholphin/services/ThemeSongPlayer.kt | 6 ++++-- .../wholphin/ui/detail/episode/EpisodeDetails.kt | 5 ++--- .../wholphin/ui/detail/movie/MovieDetails.kt | 5 ++--- .../wholphin/ui/detail/series/SeriesDetails.kt | 6 +++--- .../wholphin/ui/detail/series/SeriesOverview.kt | 6 +++--- .../wholphin/ui/detail/series/SeriesViewModel.kt | 3 --- 8 files changed, 29 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index b55b6dba..0d067f2a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -41,11 +41,13 @@ import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.services.AppUpgradeHandler import com.github.damontecres.wholphin.services.BackdropService +import com.github.damontecres.wholphin.services.DatePlayedInvalidationService import com.github.damontecres.wholphin.services.DeviceProfileService import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.PlaybackLifecycleObserver import com.github.damontecres.wholphin.services.RefreshRateService +import com.github.damontecres.wholphin.services.ServerEventListener import com.github.damontecres.wholphin.services.SetupDestination import com.github.damontecres.wholphin.services.SetupNavigationManager import com.github.damontecres.wholphin.services.UpdateChecker @@ -106,6 +108,14 @@ class MainActivity : AppCompatActivity() { @Inject lateinit var tvProviderSchedulerService: TvProviderSchedulerService + // Note: unused but injected to ensure it is created + @Inject + lateinit var serverEventListener: ServerEventListener + + // Note: unused but injected to ensure it is created + @Inject + lateinit var datePlayedInvalidationService: DatePlayedInvalidationService + private var signInAuto = true @OptIn(ExperimentalTvMaterial3Api::class) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/RefreshRateService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/RefreshRateService.kt index 67b541fc..be6e461e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/RefreshRateService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/RefreshRateService.kt @@ -95,13 +95,13 @@ class RefreshRateService } catch (ex: InterruptedException) { Timber.w(ex, "Exception waiting for refresh rate switch") } - val targetRate = (targetMode.refreshRate * 100).roundToInt() + val targetRate = (targetMode.refreshRate * 1000).roundToInt() val isSeamless = - targetRate == (display.mode.refreshRate * 100).roundToInt() || + targetRate == (currentDisplayMode.refreshRate * 1000).roundToInt() || if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - display.mode.alternativeRefreshRates - .map { (it * 100).roundToInt() } - .any { targetRate % it == 0 } + currentDisplayMode.alternativeRefreshRates + .map { (it * 1000).roundToInt() } + .any { it % targetRate == 0 } } else { false } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/ThemeSongPlayer.kt b/app/src/main/java/com/github/damontecres/wholphin/services/ThemeSongPlayer.kt index a5f54815..e6bcb867 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/ThemeSongPlayer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/ThemeSongPlayer.kt @@ -104,7 +104,9 @@ class ThemeSongPlayer } fun stop() { - Timber.v("Stopping theme song") - player.stop() + if (player.isPlaying) { + Timber.v("Stopping theme song") + player.stop() + } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt index 493093da..06216eb4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleResumeEffect -import androidx.lifecycle.compose.LifecycleStartEffect import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.model.BaseItem @@ -112,12 +111,12 @@ fun EpisodeDetails( LoadingState.Success -> { item?.let { ep -> - LifecycleStartEffect(destination.itemId) { + LifecycleResumeEffect(destination.itemId) { viewModel.maybePlayThemeSong( destination.itemId, preferences.appPreferences.interfacePreferences.playThemeSongs, ) - onStopOrDispose { + onPauseOrDispose { viewModel.release() } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt index 78876e38..6c820128 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleResumeEffect -import androidx.lifecycle.compose.LifecycleStartEffect import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.wholphin.R @@ -143,12 +142,12 @@ fun MovieDetails( LoadingState.Success -> { item?.let { movie -> - LifecycleStartEffect(destination.itemId) { + LifecycleResumeEffect(destination.itemId) { viewModel.maybePlayThemeSong( destination.itemId, preferences.appPreferences.interfacePreferences.playThemeSongs, ) - onStopOrDispose { + onPauseOrDispose { viewModel.release() } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt index 227f13bd..90c9157b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt @@ -34,7 +34,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.lifecycle.compose.LifecycleStartEffect +import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.wholphin.R @@ -124,12 +124,12 @@ fun SeriesDetails( LoadingState.Success -> { item?.let { item -> - LifecycleStartEffect(destination.itemId) { + LifecycleResumeEffect(destination.itemId) { viewModel.maybePlayThemeSong( destination.itemId, preferences.appPreferences.interfacePreferences.playThemeSongs, ) - onStopOrDispose { + onPauseOrDispose { viewModel.release() } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt index b5d4ea4b..7575e3a4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.lifecycle.compose.LifecycleStartEffect +import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.map import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.BaseItem @@ -167,12 +167,12 @@ fun SeriesOverview( }, "series_overview", ) - LifecycleStartEffect(destination.itemId) { + LifecycleResumeEffect(destination.itemId) { viewModel.maybePlayThemeSong( destination.itemId, preferences.appPreferences.interfacePreferences.playThemeSongs, ) - onStopOrDispose { + onPauseOrDispose { viewModel.release() } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt index a1a4edf6..4f85cda3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt @@ -221,9 +221,6 @@ class SeriesViewModel ) { viewModelScope.launchIO { themeSongPlayer.playThemeFor(seriesId, playThemeSongs) - addCloseable { - themeSongPlayer.stop() - } } } From 83a543ad7dc4c0e3ae044a4a0ccf9fce20d04d84 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:24:17 -0500 Subject: [PATCH 41/45] Fix some crashes due to bad UI state (#617) ## Description Fixes two possible crashes: - When saving view options for a favorite page tab - When there are no results on a grid page/tab --- .../ui/components/CollectionFolderGrid.kt | 3 +- .../wholphin/ui/detail/CardGrid.kt | 378 +++++++++--------- .../wholphin/ui/detail/ItemViewModel.kt | 2 +- .../wholphin/ui/detail/PersonPage.kt | 6 +- 4 files changed, 203 insertions(+), 186 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt index eef5c9ae..20270b94 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt @@ -169,6 +169,7 @@ class CollectionFolderViewModel context.getString(R.string.error_loading_collection, itemId), ) + Dispatchers.IO, ) { + super.itemId = itemId itemId.toUUIDOrNull()?.let { fetchItem(it) } @@ -206,7 +207,7 @@ class CollectionFolderViewModel ) { if (collectionFilter.useSavedLibraryDisplayInfo) { serverRepository.currentUser.value?.let { user -> - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launchIO { val libraryDisplayInfo = LibraryDisplayInfo( userId = user.rowId, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt index 19f8f6fd..53b6b9b8 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CardGrid.kt @@ -196,210 +196,224 @@ fun CardGrid( } } - var longPressing by remember { mutableStateOf(false) } - Row( -// horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = - modifier - .fillMaxSize() - .onKeyEvent { - if (DEBUG) Timber.d("onKeyEvent: ${it.nativeKeyEvent}") - if (useBackToJump && it.key == Key.Back && it.nativeKeyEvent.isLongPress) { - longPressing = true - val newPosition = previouslyFocusedIndex - if (DEBUG) Timber.d("Back long pressed: newPosition=$newPosition") - if (newPosition > 0) { - focusOn(newPosition) - scope.launch(ExceptionHandler()) { - gridState.scrollToItem(newPosition, -columns) - firstFocus.tryRequestFocus() - } - } - return@onKeyEvent true - } else if (it.type == KeyEventType.KeyUp) { - if (longPressing && it.key == Key.Back) { - longPressing = false - return@onKeyEvent true - } - longPressing = false - } - if (it.type != KeyEventType.KeyUp) { - return@onKeyEvent false - } else if (useBackToJump && it.key == Key.Back && focusedIndex > 0) { - jumpToTop() - return@onKeyEvent true - } else if (isPlayKeyUp(it)) { - val item = pager.getOrNull(focusedIndex) - if (item?.playable == true) { - Timber.v("Clicked play on ${item.id}") - onClickPlay.invoke(focusedIndex, item) - } - return@onKeyEvent true - } else if (useJumpRemoteButtons && isForwardButton(it)) { - jump(jump1) - return@onKeyEvent true - } else if (useJumpRemoteButtons && isBackwardButton(it)) { - jump(-jump1) - return@onKeyEvent true - } else { - return@onKeyEvent false - } - }, - ) { - if (showJumpButtons && pager.isNotEmpty()) { - JumpButtons( - jump1 = jump1, - jump2 = jump2, - jumpClick = { jump(it) }, - modifier = Modifier.align(Alignment.CenterVertically), + if (pager.isEmpty()) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier.fillMaxSize(), + ) { + Text( + text = stringResource(R.string.no_results), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center, ) } - Box( - modifier = Modifier.weight(1f), - ) { - LazyVerticalGrid( - columns = GridCells.Fixed(columns), - horizontalArrangement = Arrangement.spacedBy(spacing), - verticalArrangement = Arrangement.spacedBy(spacing), - state = gridState, - contentPadding = PaddingValues(16.dp), - modifier = - Modifier - .fillMaxSize() - .focusGroup() - .focusRestorer(firstFocus) - .focusProperties { - onExit = { - // Leaving the grid, so "forget" the position -// focusedIndex = -1 - } - onEnter = { - if (focusedIndex < 0 && gridState.firstVisibleItemIndex <= startPosition) { - focusedIndex = startPosition + } else { + var longPressing by remember { mutableStateOf(false) } + Row( +// horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = + modifier + .fillMaxSize() + .onKeyEvent { + if (DEBUG) Timber.d("onKeyEvent: ${it.nativeKeyEvent}") + if (useBackToJump && it.key == Key.Back && it.nativeKeyEvent.isLongPress) { + longPressing = true + val newPosition = previouslyFocusedIndex + if (DEBUG) Timber.d("Back long pressed: newPosition=$newPosition") + if (newPosition > 0) { + focusOn(newPosition) + scope.launch(ExceptionHandler()) { + gridState.scrollToItem(newPosition, -columns) + firstFocus.tryRequestFocus() } } - }, - ) { - items(pager.size) { index -> - val mod = - if ((index == focusedIndex) or (focusedIndex < 0 && index == 0)) { - if (DEBUG) Timber.d("Adding firstFocus to focusedIndex $index") - Modifier - .focusRequester(firstFocus) - .focusRequester(gridFocusRequester) - .focusRequester(alphabetFocusRequester) - } else { - Modifier + return@onKeyEvent true + } else if (it.type == KeyEventType.KeyUp) { + if (longPressing && it.key == Key.Back) { + longPressing = false + return@onKeyEvent true + } + longPressing = false } - val item = pager[index] - cardContent( - item, - { - if (item != null) { - focusedIndex = index - onClickItem.invoke(index, item) + if (it.type != KeyEventType.KeyUp) { + return@onKeyEvent false + } else if (useBackToJump && it.key == Key.Back && focusedIndex > 0) { + jumpToTop() + return@onKeyEvent true + } else if (isPlayKeyUp(it)) { + val item = pager.getOrNull(focusedIndex) + if (item?.playable == true) { + Timber.v("Clicked play on ${item.id}") + onClickPlay.invoke(focusedIndex, item) } - }, - { if (item != null) onLongClickItem.invoke(index, item) }, - mod - .ifElse(index == 0, Modifier.focusRequester(zeroFocus)) - .onFocusChanged { focusState -> - if (DEBUG) { - Timber.v( - "$index isFocused=${focusState.isFocused}", - ) + return@onKeyEvent true + } else if (useJumpRemoteButtons && isForwardButton(it)) { + jump(jump1) + return@onKeyEvent true + } else if (useJumpRemoteButtons && isBackwardButton(it)) { + jump(-jump1) + return@onKeyEvent true + } else { + return@onKeyEvent false + } + }, + ) { + if (showJumpButtons && pager.isNotEmpty()) { + JumpButtons( + jump1 = jump1, + jump2 = jump2, + jumpClick = { jump(it) }, + modifier = Modifier.align(Alignment.CenterVertically), + ) + } + Box( + modifier = Modifier.weight(1f), + ) { + LazyVerticalGrid( + columns = GridCells.Fixed(columns), + horizontalArrangement = Arrangement.spacedBy(spacing), + verticalArrangement = Arrangement.spacedBy(spacing), + state = gridState, + contentPadding = PaddingValues(16.dp), + modifier = + Modifier + .fillMaxSize() + .focusGroup() + .focusRestorer(firstFocus) + .focusProperties { + onExit = { + // Leaving the grid, so "forget" the position +// focusedIndex = -1 } - if (focusState.isFocused) { - // Focused, so set that up - focusOn(index) - positionCallback?.invoke(columns, index) - } else if (focusedIndex == index) { + onEnter = { + if (focusedIndex < 0 && gridState.firstVisibleItemIndex <= startPosition) { + focusedIndex = startPosition + } + } + }, + ) { + items(pager.size) { index -> + val mod = + if ((index == focusedIndex) or (focusedIndex < 0 && index == 0)) { + if (DEBUG) Timber.d("Adding firstFocus to focusedIndex $index") + Modifier + .focusRequester(firstFocus) + .focusRequester(gridFocusRequester) + .focusRequester(alphabetFocusRequester) + } else { + Modifier + } + val item = pager[index] + cardContent( + item, + { + if (item != null) { + focusedIndex = index + onClickItem.invoke(index, item) + } + }, + { if (item != null) onLongClickItem.invoke(index, item) }, + mod + .ifElse(index == 0, Modifier.focusRequester(zeroFocus)) + .onFocusChanged { focusState -> + if (DEBUG) { + Timber.v( + "$index isFocused=${focusState.isFocused}", + ) + } + if (focusState.isFocused) { + // Focused, so set that up + focusOn(index) + positionCallback?.invoke(columns, index) + } else if (focusedIndex == index) { // savedFocusedIndex = index // // Was focused on this, so mark unfocused // focusedIndex = -1 - } - }, - ) + } + }, + ) + } } - } - if (pager.isEmpty()) { + if (pager.isEmpty()) { // focusedIndex = -1 - Box(modifier = Modifier.fillMaxSize()) { - Text( - text = stringResource(R.string.no_results), - color = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.align(Alignment.Center), - ) + Box(modifier = Modifier.fillMaxSize()) { + Text( + text = stringResource(R.string.no_results), + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.align(Alignment.Center), + ) + } } - } - if (showFooter) { - // Footer - Box( - modifier = - Modifier - .align(Alignment.BottomCenter) - .background(AppColors.TransparentBlack50), - ) { - val index = (focusedIndex + 1).takeIf { it > 0 } ?: "?" + if (showFooter) { + // Footer + Box( + modifier = + Modifier + .align(Alignment.BottomCenter) + .background(AppColors.TransparentBlack50), + ) { + val index = (focusedIndex + 1).takeIf { it > 0 } ?: "?" // if (focusedIndex >= 0) { // focusedIndex + 1 // } else { // max(savedFocusedIndex, focusedIndexOnExit) + 1 // } - Text( - modifier = Modifier.padding(4.dp), - color = MaterialTheme.colorScheme.onBackground, - text = "$index / ${pager.size}", - ) + Text( + modifier = Modifier.padding(4.dp), + color = MaterialTheme.colorScheme.onBackground, + text = "$index / ${pager.size}", + ) + } } } - } - val context = LocalContext.current - val letters = context.getString(R.string.jump_letters) - // Letters - val currentLetter = - remember(focusedIndex) { - pager - .getOrNull(focusedIndex) - ?.sortName - ?.first() - ?.uppercaseChar() - ?.let { - if (it >= '0' && it <= '9') { - '#' - } else if (it >= 'A' && it <= 'Z') { - it - } else { - null - } - } - ?: letters[0] - } - if (showLetterButtons && pager.isNotEmpty()) { - AlphabetButtons( - letters = letters, - currentLetter = currentLetter, - modifier = - Modifier - .align(Alignment.CenterVertically) - .padding(end = 16.dp), - // Add end padding to push away from edge - letterClicked = { letter -> - scope.launch(ExceptionHandler()) { - val jumpPosition = - withContext(Dispatchers.IO) { - letterPosition.invoke(letter) + val context = LocalContext.current + val letters = context.getString(R.string.jump_letters) + // Letters + val currentLetter = + remember(focusedIndex) { + pager + .getOrNull(focusedIndex) + ?.sortName + ?.first() + ?.uppercaseChar() + ?.let { + if (it >= '0' && it <= '9') { + '#' + } else if (it >= 'A' && it <= 'Z') { + it + } else { + null } - Timber.d("Alphabet jump to $jumpPosition") - if (jumpPosition >= 0) { - pager.getOrNull(jumpPosition) - gridState.scrollToItem(jumpPosition) - focusOn(jumpPosition) - alphabetFocus = true } - } - }, - ) + ?: letters[0] + } + if (showLetterButtons && pager.isNotEmpty()) { + AlphabetButtons( + letters = letters, + currentLetter = currentLetter, + modifier = + Modifier + .align(Alignment.CenterVertically) + .padding(end = 16.dp), + // Add end padding to push away from edge + letterClicked = { letter -> + scope.launch(ExceptionHandler()) { + val jumpPosition = + withContext(Dispatchers.IO) { + letterPosition.invoke(letter) + } + Timber.d("Alphabet jump to $jumpPosition") + if (jumpPosition >= 0) { + pager.getOrNull(jumpPosition) + gridState.scrollToItem(jumpPosition) + focusOn(jumpPosition) + alphabetFocus = true + } + } + }, + ) + } } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/ItemViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/ItemViewModel.kt index d917b47a..65c9a0e9 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/ItemViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/ItemViewModel.kt @@ -27,7 +27,7 @@ abstract class ItemViewModel( ) : ViewModel() { val item = MutableLiveData(null) lateinit var itemId: String - lateinit var itemUuid: UUID + var itemUuid: UUID? = null suspend fun fetchItem(itemId: UUID): BaseItem = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PersonPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PersonPage.kt index 8af7ccab..b715af8d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PersonPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PersonPage.kt @@ -155,8 +155,10 @@ class PersonViewModel fun setFavorite(favorite: Boolean) { viewModelScope.launchIO { - favoriteWatchManager.setFavorite(itemUuid, favorite) - fetchAndSetItem(itemUuid) + itemUuid?.let { + favoriteWatchManager.setFavorite(it, favorite) + fetchAndSetItem(it) + } } } } From b8e8a1d9139dcde82dab4f21444e17fe6a393674 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Fri, 2 Jan 2026 11:50:23 -0500 Subject: [PATCH 42/45] Fix yet another focus problem --- .../wholphin/ui/detail/series/SeriesOverviewContent.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt index 1495285d..7f346d6c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverviewContent.kt @@ -106,6 +106,7 @@ fun SeriesOverviewContent( val scrollState = rememberScrollState() val scrollConnection = rememberDelayedNestedScroll() + var requestFocusAfterSeason by remember { mutableStateOf(false) } Box( modifier = modifier @@ -146,6 +147,7 @@ fun SeriesOverviewContent( onClick = { selectedTabIndex = it onChangeSeason.invoke(it) + requestFocusAfterSeason = true }, modifier = Modifier @@ -180,6 +182,13 @@ fun SeriesOverviewContent( } is EpisodeList.Success -> { + if (requestFocusAfterSeason) { + // Changing seasons, so move focus once the new episodes are loaded + LaunchedEffect(Unit) { + firstItemFocusRequester.tryRequestFocus() + requestFocusAfterSeason = false + } + } val state = rememberLazyListState(position.episodeRowIndex) LazyRow( From 0fe8e5d9885efe85d8b3cd4f51ef3aa187aa2dfa Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:50:45 -0500 Subject: [PATCH 43/45] Show button for trailers instead of card row (#618) ## Description On both movie & series detail pages, show a button to play trailers instead of the row of cards below. The button works like this: - If no trailers, the button shows "No trailers" and is disabled/non-clickable - If only a single trailer, the button shows "Play trailer" and clicking it plays the trailer - Otherwise, shows "Trailers" and clicking displays a dialog to choose one Local trailers are shown first in the list. Then there is an attempt to sort the trailers for the dialog so that names with "Official Trailer" or "Official Theatrical Trailer" are listed first and "Teaser" is listed last. This isn't perfect and doesn't work for non-English, but there's no other metadata to use. ### Related issues Closes #408 --- .../wholphin/data/model/Trailer.kt | 1 + .../wholphin/services/TrailerService.kt | 54 ++++++--- .../wholphin/ui/components/Dialogs.kt | 66 +++++------ .../wholphin/ui/components/PlayButtons.kt | 56 ++++++++++ .../wholphin/ui/components/TrailerDialog.kt | 51 +++++++++ .../ui/detail/episode/EpisodeDetails.kt | 2 + .../wholphin/ui/detail/movie/MovieDetails.kt | 104 +----------------- .../ui/detail/movie/MovieViewModel.kt | 10 +- .../ui/detail/series/FocusedEpisodeFooter.kt | 2 + .../ui/detail/series/SeriesDetails.kt | 29 +++-- .../ui/detail/series/SeriesViewModel.kt | 10 +- app/src/main/res/values/strings.xml | 3 + 12 files changed, 218 insertions(+), 170 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/wholphin/ui/components/TrailerDialog.kt diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/Trailer.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/Trailer.kt index bf82bbf3..943789db 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/Trailer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/Trailer.kt @@ -14,4 +14,5 @@ data class LocalTrailer( data class RemoteTrailer( override val name: String, val url: String, + val subtitle: String?, ) : Trailer diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/TrailerService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/TrailerService.kt index 1af38ddd..856493f6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/TrailerService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/TrailerService.kt @@ -22,31 +22,53 @@ class TrailerService @param:ApplicationContext private val context: Context, private val api: ApiClient, ) { - suspend fun getTrailers(item: BaseItem): List { - val remoteTrailers = - item.data.remoteTrailers - ?.mapNotNull { t -> - t.url?.let { url -> - val name = - t.name - // TODO would be nice to clean up the trailer name + fun getRemoteTrailers(item: BaseItem): List = + item.data.remoteTrailers + ?.mapNotNull { t -> + t.url?.let { url -> + val name = + t.name + // TODO would be nice to clean up the trailer name // ?.replace(item.name ?: "", "") // ?.removePrefix(" - ") - ?: context.getString(R.string.trailer) - RemoteTrailer(name, url) - } - }.orEmpty() - .sortedBy { it.name } - val localTrailerCount = item.data.localTrailerCount ?: 0 + ?: context.getString(R.string.trailer) + val subtitle = + when (url.toUri().host) { + "youtube.com", "www.youtube.com" -> "YouTube" + else -> null + } + RemoteTrailer(name, url, subtitle) + } + }.orEmpty() + .sortedWith( + compareBy( + { + // Try to show official trailers first & teasers last + when { + it.name.contains("Official Trailer", true) -> 0 + it.name.contains("Official Theatrical Trailer", true) -> 0 + it.name.contains("Teaser", true) -> 10 + it.name.contains("Trailer", true) -> 1 + else -> 5 + } + }, + { + it.name + }, + ), + ) + + suspend fun getLocalTrailers(item: BaseItem): List { + val localTrailerCount = item.data.localTrailerCount ?: return emptyList() val localTrailers = if (localTrailerCount > 0) { api.userLibraryApi.getLocalTrailers(item.id).content.map { - LocalTrailer(BaseItem.Companion.from(it, api)) + LocalTrailer(BaseItem.from(it, api)) } } else { listOf() } - return localTrailers + remoteTrailers + return localTrailers } companion object { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/Dialogs.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/Dialogs.kt index bfb098d5..4b985056 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/Dialogs.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/Dialogs.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -239,47 +240,48 @@ fun DialogPopupContent( ) { val elevatedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(elevation) - LazyColumn( + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier -// .widthIn(min = 520.dp, max = 300.dp) -// .dialogFocusable() .graphicsLayer { this.clip = true this.shape = RoundedCornerShape(28.0.dp) }.drawBehind { drawRect(color = elevatedContainerColor) } .padding(PaddingValues(24.dp)), ) { - stickyHeader { - Text( - text = title, - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - } - items(dialogItems) { - when (it) { - is DialogItemDivider -> { - HorizontalDivider(Modifier.height(16.dp)) - } + Text( + text = title, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + LazyColumn( + modifier = Modifier, + ) { + items(dialogItems) { + when (it) { + is DialogItemDivider -> { + HorizontalDivider(Modifier.height(16.dp)) + } - is DialogItem -> { - ListItem( - selected = false, - enabled = !waiting && it.enabled, - onClick = { - if (dismissOnClick) { - onDismissRequest.invoke() - } - it.onClick.invoke() - }, - headlineContent = it.headlineContent, - overlineContent = it.overlineContent, - supportingContent = it.supportingContent, - leadingContent = it.leadingContent, - trailingContent = it.trailingContent, - modifier = Modifier, - ) + is DialogItem -> { + ListItem( + selected = false, + enabled = !waiting && it.enabled, + onClick = { + if (dismissOnClick) { + onDismissRequest.invoke() + } + it.onClick.invoke() + }, + headlineContent = it.headlineContent, + overlineContent = it.overlineContent, + supportingContent = it.supportingContent, + leadingContent = it.leadingContent, + trailingContent = it.trailingContent, + modifier = Modifier, + ) + } } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/PlayButtons.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/PlayButtons.kt index 0f99f8bc..747e726a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/PlayButtons.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/PlayButtons.kt @@ -21,7 +21,10 @@ import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Refresh import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -40,6 +43,7 @@ import androidx.tv.material3.Icon import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.wholphin.R +import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.ui.FontAwesome import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.data.SortAndDirection @@ -59,10 +63,12 @@ fun ExpandablePlayButtons( resumePosition: Duration, watched: Boolean, favorite: Boolean, + trailers: List?, playOnClick: (position: Duration) -> Unit, watchOnClick: () -> Unit, favoriteOnClick: () -> Unit, moreOnClick: () -> Unit, + trailerOnClick: (Trailer) -> Unit, buttonOnFocusChanged: (FocusState) -> Unit, modifier: Modifier = Modifier, ) { @@ -134,6 +140,16 @@ fun ExpandablePlayButtons( ) } + if (trailers != null) { + item("trailers") { + TrailerButton( + trailers = trailers, + trailerOnClick = trailerOnClick, + modifier = Modifier.onFocusChanged(buttonOnFocusChanged), + ) + } + } + // More button item("more") { ExpandablePlayButton( @@ -213,6 +229,7 @@ fun ExpandableFaButton( modifier: Modifier = Modifier, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, iconColor: Color = Color.Unspecified, + enabled: Boolean = true, ) { val isFocused = interactionSource.collectIsFocusedAsState().value Button( @@ -225,6 +242,7 @@ fun ExpandableFaButton( ), contentPadding = DefaultButtonPadding, interactionSource = interactionSource, + enabled = enabled, ) { Box( modifier = @@ -251,6 +269,42 @@ fun ExpandableFaButton( } } +@Composable +fun TrailerButton( + trailers: List, + trailerOnClick: (Trailer) -> Unit, + modifier: Modifier = Modifier, +) { + var showDialog by remember { mutableStateOf(false) } + ExpandableFaButton( + title = + if (trailers.isEmpty()) { + R.string.no_trailers + } else if (trailers.size == 1) { + R.string.play_trailer + } else { + R.string.trailers + }, + iconStringRes = R.string.fa_film, + enabled = trailers.isNotEmpty(), + onClick = { + if (trailers.size == 1) { + trailerOnClick.invoke(trailers.first()) + } else { + showDialog = true + } + }, + modifier = modifier, + ) + if (showDialog) { + TrailerDialog( + onDismissRequest = { showDialog = false }, + trailers = trailers, + onClick = trailerOnClick, + ) + } +} + @PreviewTvSpec @Composable private fun ExpandablePlayButtonsPreview() { @@ -264,6 +318,8 @@ private fun ExpandablePlayButtonsPreview() { favoriteOnClick = {}, moreOnClick = {}, buttonOnFocusChanged = {}, + trailers = listOf(), + trailerOnClick = {}, modifier = Modifier, ) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/TrailerDialog.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/TrailerDialog.kt new file mode 100644 index 00000000..0b17491b --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/TrailerDialog.kt @@ -0,0 +1,51 @@ +package com.github.damontecres.wholphin.ui.components + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.res.stringResource +import androidx.tv.material3.Text +import com.github.damontecres.wholphin.R +import com.github.damontecres.wholphin.data.model.LocalTrailer +import com.github.damontecres.wholphin.data.model.RemoteTrailer +import com.github.damontecres.wholphin.data.model.Trailer + +@Composable +fun TrailerDialog( + onDismissRequest: () -> Unit, + trailers: List, + onClick: (Trailer) -> Unit, +) { + val trailersStr = stringResource(R.string.play_trailer) + val localStr = stringResource(R.string.local) + val externalStr = stringResource(R.string.external_track) + val params = + remember(trailers) { + DialogParams( + fromLongClick = false, + title = trailersStr, + items = + trailers.map { trailer -> + DialogItem( + headlineContent = { + Text(trailer.name) + }, + supportingContent = { + val subtitle = + when (trailer) { + is LocalTrailer -> localStr + is RemoteTrailer -> trailer.subtitle ?: externalStr + } + Text( + text = subtitle, + ) + }, + onClick = { onClick.invoke(trailer) }, + ) + }, + ) + } + DialogPopup( + params = params, + onDismissRequest = onDismissRequest, + ) +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt index 06216eb4..c47570f6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/episode/EpisodeDetails.kt @@ -334,6 +334,8 @@ fun EpisodeDetailsContent( } } }, + trailers = null, + trailerOnClick = {}, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt index 6c820128..6fc1cc19 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt @@ -8,9 +8,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.runtime.Composable @@ -23,24 +20,18 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.LifecycleResumeEffect -import androidx.tv.material3.MaterialTheme -import androidx.tv.material3.Text import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.ExtrasItem import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.Chapter -import com.github.damontecres.wholphin.data.model.LocalTrailer import com.github.damontecres.wholphin.data.model.Person -import com.github.damontecres.wholphin.data.model.RemoteTrailer import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.data.model.aspectRatioFloat import com.github.damontecres.wholphin.preferences.UserPreferences @@ -436,6 +427,11 @@ fun MovieDetailsContent( } } }, + trailers = trailers, + trailerOnClick = { + position = TRAILER_ROW + trailerOnClick.invoke(it) + }, modifier = Modifier .fillMaxWidth() @@ -463,21 +459,6 @@ fun MovieDetailsContent( ) } } - if (trailers.isNotEmpty()) { - item { - TrailerRow( - trailers = trailers, - onClickTrailer = { - position = TRAILER_ROW - trailerOnClick.invoke(it) - }, - modifier = - Modifier - .fillMaxWidth() - .focusRequester(focusRequesters[TRAILER_ROW]), - ) - } - } if (chapters.isNotEmpty()) { item { ChapterRow( @@ -545,78 +526,3 @@ fun MovieDetailsContent( } } } - -@Composable -fun TrailerRow( - trailers: List, - onClickTrailer: (Trailer) -> Unit, - modifier: Modifier = Modifier, -) { - val state = rememberLazyListState() - val firstFocus = remember { FocusRequester() } - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = modifier, - ) { - Text( - text = stringResource(R.string.trailers), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onBackground, - ) - LazyRow( - state = state, - horizontalArrangement = Arrangement.spacedBy(16.dp), - contentPadding = PaddingValues(horizontal = 24.dp, vertical = 8.dp), - modifier = - Modifier - .fillMaxWidth() - .focusRestorer(firstFocus), - ) { - itemsIndexed(trailers) { index, item -> - val cardModifier = - if (index == 0) { - Modifier.focusRequester(firstFocus) - } else { - Modifier - } - when (item) { - is LocalTrailer -> { - SeasonCard( - item = item.baseItem, - onClick = { onClickTrailer.invoke(item) }, - onLongClick = {}, - imageHeight = Cards.height2x3, - imageWidth = Dp.Unspecified, - showImageOverlay = false, - modifier = cardModifier, - ) - } - - is RemoteTrailer -> { - val subtitle = - when (item.url.toUri().host) { - "youtube.com", "www.youtube.com" -> "YouTube" - else -> null - } - SeasonCard( - title = item.name, - subtitle = subtitle, - name = item.name, - imageUrl = null, - isFavorite = false, - isPlayed = false, - unplayedItemCount = 0, - playedPercentage = 0.0, - onClick = { onClickTrailer.invoke(item) }, - onLongClick = {}, - modifier = cardModifier, - showImageOverlay = false, - imageHeight = Cards.height2x3, - imageWidth = Dp.Unspecified, - ) - } - } - } - } - } -} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt index be62b4aa..793ef8bb 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt @@ -25,6 +25,7 @@ import com.github.damontecres.wholphin.services.TrailerService import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.launchIO +import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.util.ExceptionHandler @@ -116,16 +117,19 @@ class MovieViewModel item, userPreferencesService.getCurrent(), ) + val remoteTrailers = trailerService.getRemoteTrailers(item) withContext(Dispatchers.Main) { this@MovieViewModel.item.value = item chosenStreams.value = result + this@MovieViewModel.trailers.value = remoteTrailers loading.value = LoadingState.Success backdropService.submit(item) } viewModelScope.launchIO { - val trailers = trailerService.getTrailers(item) - withContext(Dispatchers.Main) { - this@MovieViewModel.trailers.value = trailers + trailerService.getLocalTrailers(item).letNotEmpty { localTrailers -> + withContext(Dispatchers.Main) { + this@MovieViewModel.trailers.value = localTrailers + remoteTrailers + } } } viewModelScope.launchIO { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/FocusedEpisodeFooter.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/FocusedEpisodeFooter.kt index ddaedd88..9b6d81df 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/FocusedEpisodeFooter.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/FocusedEpisodeFooter.kt @@ -45,6 +45,8 @@ fun FocusedEpisodeFooter( watchOnClick = watchOnClick, favoriteOnClick = favoriteOnClick, buttonOnFocusChanged = buttonOnFocusChanged, + trailers = null, + trailerOnClick = {}, modifier = Modifier.fillMaxWidth(), ) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt index 90c9157b..7546ca56 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt @@ -62,6 +62,7 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage import com.github.damontecres.wholphin.ui.components.Optional import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.SeriesQuickDetails +import com.github.damontecres.wholphin.ui.components.TrailerButton import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo @@ -70,7 +71,6 @@ import com.github.damontecres.wholphin.ui.detail.PlaylistDialog import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson -import com.github.damontecres.wholphin.ui.detail.movie.TrailerRow import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.rememberInt @@ -411,6 +411,18 @@ fun SeriesDetailsContent( } }, ) + TrailerButton( + trailers = trailers, + trailerOnClick = trailerOnClick, + modifier = + Modifier.onFocusChanged { + if (it.isFocused) { + scope.launch(ExceptionHandler()) { + bringIntoViewRequester.bringIntoView() + } + } + }, + ) } } item { @@ -472,21 +484,6 @@ fun SeriesDetailsContent( ) } } - if (trailers.isNotEmpty()) { - item { - TrailerRow( - trailers = trailers, - onClickTrailer = { - position = TRAILER_ROW - trailerOnClick.invoke(it) - }, - modifier = - Modifier - .fillMaxWidth() - .focusRequester(focusRequesters[TRAILER_ROW]), - ) - } - } if (extras.isNotEmpty()) { item { ExtrasRow( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt index 4f85cda3..181e5b98 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt @@ -165,8 +165,9 @@ class SeriesViewModel } } } - + val remoteTrailers = trailerService.getRemoteTrailers(item) withContext(Dispatchers.Main) { + this@SeriesViewModel.trailers.value = remoteTrailers this@SeriesViewModel.position.update { it.copy( episodeRowIndex = @@ -179,9 +180,10 @@ class SeriesViewModel } if (seriesPageType == SeriesPageType.DETAILS) { viewModelScope.launchIO { - val trailers = trailerService.getTrailers(item) - withContext(Dispatchers.Main) { - this@SeriesViewModel.trailers.value = trailers + trailerService.getLocalTrailers(item).letNotEmpty { localTrailers -> + withContext(Dispatchers.Main) { + this@SeriesViewModel.trailers.value = localTrailers + remoteTrailers + } } } viewModelScope.launchIO { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4e4a859..db4e720d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -399,6 +399,9 @@ Subtitle delay Backdrop style Resolution switching + Local + Play trailer + No trailers Disabled From 4d9e3cb6de14adde20b69d104f87473c10b1ef22 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:13:03 -0500 Subject: [PATCH 44/45] Fix MPV race condition causing no video output (#619) ## Description Fixes a race condition where `libmpv` is beginning playback before it has been attached to the output video surface. The solution is for the handler to re-enqueue commands if MPV is either not initialized or not attached yet. Once `libmpv` is ready, the commands will be processed. ### Related issues Should fix #576 --- .../wholphin/util/mpv/MpvPlayer.kt | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt index 4777e977..81eb8669 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt @@ -109,6 +109,7 @@ class MpvPlayer( Timber.v("config-dir=${context.filesDir.path}") MPVLib.addLogObserver(mpvLogger) + Timber.v("Creating MPVLib") MPVLib.create(context) MPVLib.setOptionString("config", "yes") MPVLib.setOptionString("config-dir", context.filesDir.path) @@ -127,6 +128,7 @@ class MpvPlayer( MPVLib.setOptionString("demuxer-max-bytes", "${cacheMegs * 1024 * 1024}") MPVLib.setOptionString("demuxer-max-back-bytes", "${cacheMegs * 1024 * 1024}") + Timber.v("Initializing MPVLib") MPVLib.initialize() MPVLib.setOptionString("force-window", "no") @@ -466,7 +468,6 @@ class MpvPlayer( override fun clearVideoSurfaceHolder(surfaceHolder: SurfaceHolder?): Unit = throw UnsupportedOperationException() override fun setVideoSurfaceView(surfaceView: SurfaceView?) { - throwIfReleased() if (DEBUG) Timber.v("setVideoSurfaceView") val surface = surfaceView?.holder?.surface if (surface != null && surface.isValid) { @@ -635,9 +636,9 @@ class MpvPlayer( val title = it.label ?: "External Subtitles" Timber.v("Adding external subtitle track '$title'") if (it.language.isNotNullOrBlank()) { - MPVLib.command(arrayOf("sub-add", url, "auto", title, it.language!!)) + MPVLib.command(arrayOf("sub-add", url, "select", title, it.language!!)) } else { - MPVLib.command(arrayOf("sub-add", url, "auto", title)) + MPVLib.command(arrayOf("sub-add", url, "select", title)) } } } @@ -837,7 +838,18 @@ class MpvPlayer( override fun handleMessage(msg: Message): Boolean { val cmd = MpvCommand.entries[msg.what] - Timber.v("handleMessage: cmd=$cmd") + Timber.d("handleMessage: cmd=$cmd") + if (isReleased && cmd != MpvCommand.DESTROY) { + Timber.w("Player is released, ignoring command %s", cmd) + return true + } + if (surface == null && !cmd.isLifecycle) { + // If libmpv isn't ready, re-enqueue the messages + // Note: this means nothing will play until it is attached to a surface, + // so MpvPlayer can't be used for background audio/music playback + Timber.v("MPV is not initialized/attached yet, requeue cmd %s", cmd) + internalHandler.sendMessageDelayed(Message.obtain(msg), 50) + } when (cmd) { MpvCommand.PLAY_PAUSE -> { val playWhenReady = msg.obj as Boolean @@ -894,6 +906,7 @@ class MpvPlayer( MPVLib.detachSurface() MPVLib.setPropertyString("vo", "null") MPVLib.setPropertyString("force-window", "no") + Timber.d("Detached surface") } if (surface != null) { MPVLib.attachSurface(surface) @@ -1035,14 +1048,16 @@ private data class MediaAndPosition( val startPositionMs: Long, ) -enum class MpvCommand { - PLAY_PAUSE, - SEEK, - SET_TRACK_SELECTION, - SET_SPEED, - SET_SUBTITLE_DELAY, - LOAD_FILE, - ATTACH_SURFACE, - INITIALIZE, - DESTROY, +enum class MpvCommand( + val isLifecycle: Boolean, +) { + PLAY_PAUSE(false), + SEEK(false), + SET_TRACK_SELECTION(false), + SET_SPEED(false), + SET_SUBTITLE_DELAY(false), + LOAD_FILE(false), + ATTACH_SURFACE(true), + INITIALIZE(true), + DESTROY(true), } From 36d26a5ead0a1c6c39525b6e7df40ba604491c41 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Fri, 2 Jan 2026 15:52:36 -0500 Subject: [PATCH 45/45] Release v0.3.10