Releases: mhoogenbosch/PiPup
Release list
v0.21.1 — the self-update TLS fix now reaches the redirect hop
EN — Completes the Android 6 self-update fix from 0.19.3 (#41). The bundled ISRG Root X1 never protected the actual download: the GitHub asset URL redirects from github.com (USERTrust-anchored — fine on Android 6) to release-assets.githubusercontent.com (ISRG-anchored), and HttpURLConnection's automatic redirect creates a fresh connection that does not inherit the custom SSLSocketFactory. The updater now follows redirects manually (max 5 hops) and applies its trust store to every hop. Also new: /state.update.tlsFactory reports which trust store the updater uses (composite, platform-default: <why>, or unbuilt before the first check), so TLS problems on a remote device are diagnosable without adb.
NL — Maakt de Android 6-self-update-fix van 0.19.3 af (#41). De gebundelde ISRG Root X1 beschermde de echte download nooit: de asset-URL redirect van github.com (USERTrust — prima op Android 6) naar release-assets.githubusercontent.com (ISRG-geankerd), en de automatische redirect van HttpURLConnection maakt een nieuwe verbinding die de eigen SSLSocketFactory niet erft. De updater volgt redirects nu handmatig (max 5 hops) en zet zijn trust store op élke hop. Nieuw: /state.update.tlsFactory toont welke trust store de updater gebruikt — TLS-problemen op afstand zijn zo zonder adb te diagnosticeren.
Full changelog: CHANGELOG.md
v0.21.0 — the TV tells you when the HA integration is behind
EN — The app now knows which version of the ha-pipup Home Assistant integration should be talking to it — and which one actually is.
/stategains ahaPipupobject:recommended(the latest ha-pipup release, fetched alongside the hourly self-update check — the recommendation is by definition the latest release, so it is never maintained by hand),minimum(the oldest integration that can drive this app's full API; a build-time constant, currently 1.17.1) andconnected(ha-pipup ≥ 1.18.0 announces itself with anX-HA-PiPup-Versionheader on every request, including the 15-second/statepoll — so this fills in without a single popup being sent).- The app's status screen shows the same information as one line: ✓ up to date, update available, too old for this app, or the recommended version when no integration has been seen yet.
NL — De app weet nu welke versie van de ha-pipup-integratie met hem hóórt te praten — en welke dat daadwerkelijk doet.
/statekrijgt eenhaPipup-object:recommended(de nieuwste ha-pipup-release, opgehaald met de uurlijkse updatecheck — de aanbeveling ís per definitie de laatste release, er wordt niets met de hand bijgehouden),minimum(de oudste integratie die de volledige API van deze app aankan; bouwconstante, nu 1.17.1) enconnected(ha-pipup ≥ 1.18.0 meldt zich met eenX-HA-PiPup-Version-header op elk request, óók de 15-seconden/state-poll — dit vult zich dus zonder dat er één popup verstuurd wordt).- Het statusscherm van de app toont hetzelfde als één regel: ✓ up to date, update beschikbaar, te oud voor deze app, of de aanbevolen versie zolang er nog geen integratie gezien is.
Full changelog: CHANGELOG.md
v0.20.1 — the poster fallback no longer races a slow player
🇬🇧 Fixed
0.20.0's 8-second fallback could still fade the poster into a black player shell on a slow device: on a TCL Google TV the page committed after 5.7 s and the cap fired at 13.7 s — occasionally just before the video's first frame. The watcher now reports when it finds a <video> element, and such a page is exempt from the cap: its poster simply stays until the video actually plays, however long that takes. The (now 20-second) cap only remains for pages whose watcher reports nothing at all.
🇳🇱 Opgelost
Het 8-seconden-vangnet van 0.20.0 kon de poster op een traag toestel alsnog in een zwart spelerscherm laten vervagen: op een TCL Google TV kwam de pagina-commit na 5,7 s en vuurde het plafond op 13,7 s — soms nét voor het eerste videobeeld. De watcher meldt nu wanneer hij een <video>-element vindt, en zo'n pagina is vrijgesteld van het plafond: de poster blijft gewoon staan tot de video echt speelt, hoe lang dat ook duurt. Het (nu 20 seconden-)plafond geldt alleen nog voor pagina's waarvan de watcher helemaal niets meldt.
v0.20.0 — the poster waits for the video on web popups
🇬🇧 Changed
On a web_url popup the poster now fades when the page's video actually plays, not when the page paints. For an MJPEG or snapshot page nothing changes (its paint is the image). But a player page such as go2rtc's WebRTC viewer paints its shell seconds before the stream flows — 0.19.x faded the poster at that paint and left a black hole until WebRTC connected. The hand-over is fully dynamic now: measured on a Fire TV, a cold WebRTC stream keeps its poster until the true video start (5.7 s), a warm one hands over at 1.1 s. A hard 8-second cap covers broken pages. firstFrameMs measures to actual playback.
This makes WebRTC + poster the best camera route on TVs: an instant still, then near-realtime video (sub-second lag, full frame rate), with the slow WebRTC start-up fully masked.
🇳🇱 Gewijzigd
Bij een web_url-popup vervaagt de poster nu op het moment dat de video op de pagina écht speelt, niet wanneer de pagina schildert. Voor een MJPEG- of snapshot-pagina verandert er niets (het schilderen is daar het beeld). Maar een spelerpagina zoals go2rtc's WebRTC-viewer schildert zijn casco seconden vóór de stream loopt — 0.19.x liet de poster op dat moment al vervagen en toonde een zwart gat tot WebRTC verbonden was. De overdracht is nu volledig dynamisch: gemeten op een Fire TV houdt een koude WebRTC-stream zijn poster tot de echte videostart (5,7 s), een warme draagt over op 1,1 s. Een plafond van 8 seconden vangt kapotte pagina's af. firstFrameMs meet tot het echte afspelen.
Daarmee is WebRTC + poster de beste cameraroute op TV's: direct een still, daarna vrijwel realtime video (minder dan een seconde achterstand, volle framerate), met de trage WebRTC-opstart volledig gemaskeerd.
v0.19.3 — self-update works on Android 6 (ISRG Root X1 bundled)
🇬🇧 Fixed
Self-update failed on Android 6 with Trust anchor for certification path not found (#41). GitHub's release assets live on *.githubusercontent.com, whose TLS chain anchors on ISRG Root X1 (Let's Encrypt) — a root Android only ships from 7.1.1. That is why the update check (api.github.com, USERTrust root) succeeded while the download failed. The updater's connections now trust the system store plus the bundled ISRG Root X1, with full chain validation either way — no trust-all anywhere; on modern devices nothing changes. Integrity stays double-locked regardless: the platform refuses an update APK with a different signing certificate or a lower versionCode.
Note: this release itself still has to be installed once over adb on Android 6 (install.sh <ip>); from the next release on, the in-app updater works there too.
🇳🇱 Opgelost
Zelf-updaten faalde op Android 6 met Trust anchor for certification path not found (#41). GitHubs release-bestanden staan op *.githubusercontent.com, waarvan de TLS-keten verankerd is op ISRG Root X1 (Let's Encrypt) — een root die Android pas vanaf 7.1.1 meelevert. Daarom slaagde de update-check (api.github.com, USERTrust-root) wel terwijl de download faalde. De updater-verbindingen vertrouwen nu de systeem-store plus de meegeleverde ISRG Root X1, met volledige ketenvalidatie in beide gevallen — nergens trust-all; op moderne toestellen verandert er niets. De integriteit blijft dubbel geborgd: het platform weigert een update-APK met een andere handtekening of een lagere versionCode.
NB: deze release zelf moet op Android 6 nog één keer via adb (install.sh <ip>); vanaf de volgende release werkt de in-app-updater daar ook.
v0.19.2 — compact buttons: the margins scale along
🇬🇧 Fixed
With buttonSize set, the margins around the buttons — including the gap between the media frame and the button row — stayed at the classic size while the buttons themselves shrank, so a compact popup carried a full-size gap above its buttons. The margins now scale with the same factor as the button padding; the classic look (no buttonSize) is unchanged.
🇳🇱 Opgelost
Met buttonSize gezet bleven de marges rond de knoppen — inclusief de ruimte tussen het mediakader en de knoppenrij — op de klassieke maat staan terwijl de knoppen zelf krompen; een compacte popup hield dus een groot gat boven zijn knoppen. De marges schalen nu met dezelfde factor mee als de knop-padding; de klassieke look (zonder buttonSize) is ongewijzigd.
v0.19.1 — button corners unclipped, padding field, install errors visible
🇬🇧 Fixed / Added
- Buttons' bottom rounded corners were clipped against the popup border (screenshot in #40): the button row now has a bottom margin, and the popup no longer clips its children — which also lets the focus scale-up and the slide animations paint past the edges cleanly.
padding(px): the popup's outer margin around content. Classic look is 20;0gives a near-borderless popup.- A synchronous self-update failure was invisible (#41): a failed download (HTTP error, or an exception such as DNS/TLS/an old certificate store on Android 6) or a
PackageInstallercommit exception only went to logcat, while/state.update.errorstayednull— the update just seemed to do nothing. Those failures now show up inupdate.error(and in the Home Assistant update entity'sinstall_error); a successful commit clears it.
🇳🇱 Opgelost / Toegevoegd
- De onderste ronde hoeken van de knoppen werden afgesneden door de popuprand (screenshot in #40): de knoppenrij heeft nu een ondermarge en de popup knipt zijn kinderen niet meer af — daardoor lopen ook de focus-vergroting en de slide-animaties netjes over de randen.
padding(px): de buitenmarge van de popup rond de inhoud. Klassiek is 20;0geeft een vrijwel randloze popup.- Een synchrone zelf-update-fout was onzichtbaar (#41): een mislukte download (HTTP-fout, of een exception zoals DNS/TLS/een verouderde certificaatstore op Android 6) of een
PackageInstaller-commit-exception kwam alleen in logcat, terwijl/state.update.erroropnullbleef — de update leek gewoon niets te doen. Die fouten staan nu inupdate.error(en in hetinstall_error-attribuut van de Home Assistant-update-entiteit); een geslaagde commit wist ze.
v0.19.0 — compact buttons, popup animations, TCL keep-alive by default
🇬🇧 What changed
Requested in #40 (items 1 and 2; stacking stays parked — see the issue).
Added
buttonSize(sp): scales the button text and padding together, so a popup with buttons can be genuinely small — three buttons no longer dictate the popup's minimum width. Without the field the classic look is unchanged.animation:fade,slide_left,slide_right,slide_top,slide_bottom. Plays when a popup is built; an update-in-place of the same popup deliberately does not re-animate (a camera popup re-notified every few seconds must not keep sliding in). A popup that expires naturally animates out the same way; a replace,/cancelor button press still tears down instantly, so "200 = gone from/state" (0.17.1) holds. Unknown names show instantly./state.lastPopup.animationechoes the field.
Changed
install.sh/install.ps1enable the accessibility service by default on TCL Google TVs (--no-accessibility/-NoAccessibilityto opt out). A system-bound accessibility service keeps the process atoom_score_adj100 ("visible"), out of reach of TCL's vendor guard that kills and freezes background apps — measured here and confirmed by a second owner whose TCL had been dropping off for weeks (#38). The service itself stays dormant. The README's TCL section now leads with this.
Pairs with ha-pipup 1.17.0 (button_size, animation on pipup.show + per-TV defaults).
🇳🇱 Wat is er veranderd
Gevraagd in #40 (punt 1 en 2; stapelen blijft geparkeerd — zie het issue).
Toegevoegd
buttonSize(sp): schaalt knoptekst en padding samen, zodat een popup met knoppen echt klein kan — drie knoppen bepalen niet langer de minimale breedte. Zonder het veld blijft de klassieke look ongewijzigd.animation:fade,slide_left,slide_right,slide_top,slide_bottom. Speelt als een popup wordt opgebouwd; een update-in-place van dezelfde popup animeert bewust niet opnieuw (een camerapopup die om de paar seconden opnieuw wordt gestuurd moet niet blijven inschuiven). Een popup die vanzelf afloopt animeert omgekeerd weer uit; vervangen,/cancelof een knopdruk ruimt direct op, zodat "200 = weg uit/state" (0.17.1) blijft gelden. Onbekende namen verschijnen direct./state.lastPopup.animationgeeft het veld terug.
Gewijzigd
install.sh/install.ps1zetten op TCL Google TV's de accessibility-service standaard aan (--no-accessibility/-NoAccessibilityom te weigeren). Een door het systeem gebonden accessibility-service houdt het proces opoom_score_adj100 ("visible"), buiten bereik van TCL's guard die achtergrond-apps killt en bevriest — hier gemeten en bevestigd door een tweede eigenaar wiens TCL wekenlang wegviel (#38). De service zelf blijft slapend. De TCL-sectie in de README begint er nu mee.
Hoort bij ha-pipup 1.17.0 (button_size, animation op pipup.show + per-TV-standaarden).
v0.18.1 — the built-in chime is actually audible over HDMI
🇬🇧 Fixed
The 0.18.0 chime (0.42 s) played — logcat showed every frame delivered — but was inaudible on a Nokia 8010 feeding a soundbar: an HDMI/eARC audio path takes a few hundred milliseconds to open when a new stream starts, and the whole chime fell into that gap. The built-in default sound is now 1.9 s with a 300 ms silent lead-in and a fuller three-note tail at full level; the same clip, hosted as a URL, was clearly audible on that TV. If you use your own sound URL, give it a short silent lead-in too.
🇳🇱 Opgelost
Het belletje van 0.18.0 (0,42 s) werd wél afgespeeld — logcat toonde elk frame — maar was onhoorbaar op een Nokia 8010 met een soundbar: een HDMI/eARC-audiopad heeft een paar honderd milliseconden nodig om open te gaan als een nieuwe stream start, en het hele belletje viel in dat gat. Het ingebouwde default-geluid is nu 1,9 s met 300 ms stille aanloop en een vollere drietoons-staart op vol niveau; hetzelfde fragment als URL was op die TV duidelijk hoorbaar. Gebruik je een eigen sound-URL, geef die dan ook een korte stille aanloop.
v0.18.0 — seen over the screensaver; optional notification sound
🇬🇧 What changed
Requested in #34: popups were invisible while the Android TV screensaver / ambient mode was showing, and a chime would help draw attention when someone is at the door.
Screensaver. Measured on a Google TV (Android 11) the popup overlay actually sits above the screensaver's DreamActivity and is visible — but on other builds the dream layer is higher, and Android 12+ lets a screensaver hide all app overlays. An app cannot raise its own z-order, so the fix that works everywhere is to end the screensaver when a popup arrives (the same wake path as POST /power), so the popup shows on whatever was behind it. The service tracks DREAMING_STARTED/STOPPED; /state now reports dreaming. New field dismissScreensaver (default true; set false to keep the screensaver — the popup may then be invisible on such devices).
Sound. New field sound: "default" plays a built-in short chime, any other value is a URL/URI of an audio clip (mp3/ogg/wav); soundVolume (0–1) scales it. Played once when a popup is newly built — an update-in-place of the same popup does not replay it, so a motion popup re-notified every few seconds stays quiet. Transient audio focus with ducking: the TV's audio dips and comes back. Like tts this opens an audio path, and some Fire TVs renegotiate HDMI audio briefly when that happens — hence opt-in. Both fields also work on multipart. /state.lastPopup.sound tells whether a sound was requested.
Pairs with ha-pipup 1.16.0 (sound, sound_volume, dismiss_screensaver on pipup.show, plus per-TV defaults).
🇳🇱 Wat is er veranderd
Gevraagd in #34: popups waren onzichtbaar zolang de Android TV-screensaver / ambient mode aan stond, en een belletje helpt de aandacht te trekken als er iemand aan de deur staat.
Screensaver. Gemeten op een Google TV (Android 11) staat de popup-overlay juist boven de DreamActivity van de screensaver en is hij zichtbaar — maar op andere builds ligt de dream-laag hoger, en Android 12+ laat een screensaver alle app-overlays verbergen. Een app kan zijn eigen z-order niet verhogen, dus de oplossing die overal werkt is de screensaver beëindigen zodra er een popup komt (dezelfde wekroute als POST /power), zodat de popup te zien is op wat erachter zat. De service volgt DREAMING_STARTED/STOPPED; /state meldt nu dreaming. Nieuw veld dismissScreensaver (standaard true; false laat de screensaver staan — de popup kan dan op zulke toestellen onzichtbaar zijn).
Geluid. Nieuw veld sound: "default" speelt een ingebouwd kort belletje, elke andere waarde is een URL/URI van een audiofragment (mp3/ogg/wav); soundVolume (0–1) schaalt het. Eén keer afgespeeld als een popup nieuw wordt opgebouwd — een update-in-place van dezelfde popup speelt het niet opnieuw, zodat een bewegingspopup die om de paar seconden opnieuw wordt gestuurd stil blijft. Tijdelijke audio-focus met ducking: het TV-geluid dipt en komt terug. Net als tts opent dit een audiopad, en sommige Fire TV's hernegotiëren daarbij kort de HDMI-audio — daarom opt-in. Beide velden werken ook op multipart. /state.lastPopup.sound geeft aan of er om een geluid gevraagd is.
Hoort bij ha-pipup 1.16.0 (sound, sound_volume, dismiss_screensaver op pipup.show, plus standaardwaarden per TV).