Player: Sticky-Footer-Player für seitenübergreifende Persistenz (static site) #53
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
Reviewed/Confirmed
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Status/Abandoned
Status/Blocked
Status/Need More Info
Status/Needs-v00-Abnahme
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Zentonic/zentonic-publisher#53
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
███████░░░████████░░████████░░████████░░█████░░░░░Problem
Auf der statischen Site führt jede Seitennavigation zu einem vollen Page-Reload. Der
<audio>-Player wird zerstört, das Audio stoppt.Lösungsansätze
A) sessionStorage + Sticky Footer Player (empfohlen)
base.html.j2)sessionStorage.ztPlayer = {src, title, currentTime, slug}timeupdate-Event aktualisiertsessionStorage.ztPlayer.currentTimekontinuierlichB) SPA-Navigation
fetch()laden, Content per DOM-Swap einfügenEmpfehlung
Option A — passt zum Charakter der statischen Site, kein Framework nötig. sessionStorage deckt den Tab, kein Cross-Tab-Sync nötig.
Hinweise
Sticky-Footer-Player gemerged — bereit zur v00-Abnahme
Ansatz A (sessionStorage + globaler Sticky-Footer-Player in
base.html.j2) implementiert, gemerged nachdev(Merge-Commit26a04d5, feat-Branchfeat/53-sticky-player).Umgesetzt:
.zt-sticky-player__*).sessionStorage.ztPlayer({kind,src,title,slug,currentTime,playing}), try/catch-gekapselt (Private-Mode-safe).new URL(audio_page_url, location.href).hrefabsolut aufgelöst → funktioniert seitenübergreifend (index-Root ↔ episodes/), kein base_href-Bruch..play()beim Rehydrieren — nur Versuch beiplaying:truemit.catch()-Fallback auf sichtbaren Paused-Zustand.body.zt-has-inline-playerversteckt, läuft im Hintergrund mit),prefers-reduced-motionrespektiert.kind-Feld, Quelle nicht hart auf Episode verdrahtet.Build verifiziert (fehlerfrei, 6 Episoden + index/feed/sitemap/robots). Kein
sample_project/publicangerührt.Für v00-Abnahme (Headless/Pixel):
prefers-reduced-motionsessionStoragekomplett (statt nur ausblenden).P02-Nachbesserung gemerged — Footer nicht mehr verdeckt
Gemerged nach
dev(f095e4b, Branchfeat/53b-player-padding).Konditionales
padding-bottom: 72pxam body, nur wenn Sticky-Player sichtbar/aktiv:body.zt-has-inline-player, Sticky perdisplay:noneversteckt) ausgenommen → dort kein padding.:has()reicht).Hinweis für Abnahme:
:has()= moderne Browser (Chrome/Edge/Safari, Firefox ≥121). Für den kosmetischen Scope ok; JS-Fallback bei Bedarf nachrüstbar.Bereit zur finalen v00-Pixel-Abnahme (Footer sichtbar bei aktivem Player, kein Leerraum sonst) → danach #53 schließbar.
v00-Abnahme #53 — PASS ✓ (final, browser-verifiziert)
Nachbesserung (P02 Footer-padding) auf
f095e4bper Headless-CDP getestet:Zustandsgesteuert via
body:has(.zt-sticky-player.is-active):not(.zt-has-inline-player)(style.css:1198). Persistenz-Kern (sessionStorage-Rehydrate, currentTime erhalten) +prefers-reduced-motionweiterhin PASS. Kein Blocker → geschlossen durch v00.Wiedereröffnet — Design-Korrektur: durchgängiger Sticky-Footer-Player
v00-Nachtest (CDP, echtes Layout) hat den eigentlichen Punkt geklärt:
index.htmlklebt der Sticky-Footer technisch korrekt unten (position:fixed; bottom:0, rect am Viewport-Boden — belegt).body.zt-has-inline-player .zt-sticky-player{display:none}, style.css:1189), stattdessen sitzt der große Inline-Player mitten im Content.Holm-Entscheidung: Player soll durchgängig unten kleben — auch auf der Episodenseite. Der mittige Inline-Player entfällt zugunsten des Sticky-Footers als einziger, seitenübergreifend persistenter Player-Instanz.
Umbau (Details im Pxx an o00): Sticky-Footer = einziges
<audio>, auf allen Seiten unten sichtbar. Episodenseite behält Cover/Meta +folge abspielen+download+ Shownotes + Kapitel;folge abspielenund Kapitelmarken steuern den Sticky-Player (src/currentTime/play), kein separater Inline-<audio>mehr. sessionStorage-Persistenz bleibt (wird sogar einfacher — keine Zwei-Player-Sync).Bekannte, unveränderte Grenze (Browser-Limit, kein Bug): nahtloses Weiterspielen über einen echten Seitenwechsel bleibt bei statischer Multi-Page nicht möglich — nach Navigation 1× Play zum Fortsetzen (springt an die gemerkte Position). Betrifft nur das Weiterspielen, nicht die jetzt durchgängige Position.
War von v00 verfrüht als PASS geschlossen (Position nicht als Anforderung erkannt). Jetzt sauber nachgezogen.
P04 gemerged — Sticky-Footer ist jetzt die einzige Audio-Instanz
Gemerged nach
dev(49a2ca1, Branchfeat/53c-sticky-everywhere). Netto −407 Zeilen (toter Inline-Player + Sync-Brücke entfernt).Umbau:
<audio id="zt-audio">+ Seekbar/Zeit/Play-UI + JS) ausepisode.html.j2entfernt (0 Trefferid="zt-audio"im Output).window.ztStickyPlayer:loadEpisode({src,title,slug})(sichtbar, pausiert, Resume-Schutz gegen identische sessionStorage-Episode),play(),seekTo(sec)(wartet aufloadedmetadatabeipreload=none).folge abspielen→loadEpisode()+play()(Klick=User-Geste). Episodenseite bereitet den Sticky-Player beim Laden vor (src/title gesetzt, sichtbar, pausiert).seekTo()+play(), Smooth-Scroll zum Footer.zt-has-inline-playerrestlos entfernt (0 Treffer site-/templateweit), totes Inline-CSS entfernt. P02-Padding vereinfacht:body:has(.zt-sticky-player.is-active){padding-bottom:72px}(gilt jetzt auch auf Episode).Grenze (kein Bug, wie besprochen): nahtloses Weiterspielen über echten Seitenwechsel bleibt browser-limitiert (nach Nav 1× Play, springt an gemerkte Position).
Bereit zur v00-CDP-Abnahme: Sticky-Player unten fixiert auf index+Episode,
folge abspielenstartet unten, Kapitelklick springt+scrollt, Footer nicht verdeckt, Persistenz/Stream/Chat intakt → danach #53 schließbar.v00-Abnahme #53 (P04-Umbau) — FAIL, Nachbesserung nötig
Struktureller Umbau ist korrekt (Inline-Player entfernt, Sticky = einzige Instanz,
folge abspielen+ Kapitel sollen ihn steuern). Aber funktional tot — CDP-verifiziert:hidden:true,srcleer.folge abspielenklicken → nichts: keinis-active,paused:true,srcleer, sessionStorage leer.currentTimebleibt 0.Root Cause: Script-Reihenfolge
Das Episode-Steuerungs-Script (mit
loadEpisode()+ Play-Button-Handler) ist ein synchrones IIFE mitif (!window.ztStickyPlayer) return;und steht im Content — vor dem Sticky-Player-Script am Body-Ende, daswindow.ztStickyPlayer = { loadEpisode, play, seekTo }erst setzt (render episodes/*.html: Steuerungs-Script Z.151, Export Z.570). Es läuft also, wennwindow.ztStickyPlayernochundefinedist →return, der Play-Button-Handler wird nie registriert,loadEpisode()nie aufgerufen. Der Kapitel-Handler ist bereits inDOMContentLoadedgewrappt (feuert korrekt), wirkt aber auf einsrc-loses Audio.Fix
In
templates/zentonic26/episode.html.j2: das Episode-Steuerungs-Script (das mitzt-ep-play-btn+window.ztStickyPlayer.loadEpisode(...)) indocument.addEventListener('DOMContentLoaded', function(){ … })wrappen — genau wie der Kapitel-Handler direkt darunter es schon tut. Da der Sticky-Player-Script ein synchrones IIFE ist (setztwindow.ztStickyPlayerbeim Parsen, vorDOMContentLoaded), ist das Objekt dann garantiert vorhanden. (Alternativ das Script ans Seitenende nach dem Sticky-Script verschieben — DOMContentLoaded-Wrap ist minimal-invasiv.)Verify-Pflicht (Worker)
Nicht nur „sieht richtig aus" — real testen (CDP/Browser):
folge abspielenstartet den Sticky-Player (sichtbar,is-active,playing,srcgesetzt); Kapitelsprung setztcurrentTime; Persistenz index↔Episode; Stream/Chat intakt.P05 gemerged — Script-Reihenfolge gefixt, jetzt funktional (echter Browser-Test)
Gemerged nach
dev(780da1f, Branchfix/53d-script-order).Root Cause (v00-CDP-Befund bestätigt): Das Episode-Steuerungs-Script war ein synchrones IIFE und lief im Parse-Fluss vor der
window.ztStickyPlayer-Definition (die im body nach{% block content %}liegt) →if (!window.ztStickyPlayer) return;griff immer → Play-Button nie verdrahtet,loadEpisode()nie gerufen.Fix: Steuerungs-Script in
document.addEventListener('DOMContentLoaded', …)gewrappt (analog zum bereits korrekten Kapitel-Handler). Guard als defensiver Fallback belassen. Nurepisode.html.j2+ Rebuild.ECHTER Laufzeit-Test (Headless-Chromium via CDP, Trusted-Input-Klick):
window.ztStickyPlayerbei DOMContentLoaded definiert ✓folge abspielen→#zt-sticky-audio.src = https://demo.podcast.zentonic.org/audio/001.mp3, Playeris-active+ sichtbar,audio.paused:false,readyState:4,currentTimeläuft real (1.24s) ✓seekTo(),currentTimespringt auf 2.06s, Wiedergabe läuft weiter ✓Kein statischer Fallback — Runtime-Beweis liegt vor (Trusted-Input, nicht
.click()).Bereit zur finalen v00-CDP-Abnahme (ggf. Rückkehr via sessionStorage + mehrere Episodenseiten) → danach #53 endlich zu.
v00-Abnahme #53 — PASS (durchgängiger Sticky-Footer, laufzeit-bewiesen)
P05-Fix (Steuerungs-Script in DOMContentLoaded) auf
780da1f, CDP-verifiziert + visuell:"Player mitten in der Seite" ist behoben — der Sticky-Footer klebt jetzt auf allen Seiten unten. Kein Blocker, geschlossen durch v00.
Bekannte Grenze (kein Bug): nahtloses Weiterspielen ueber einen echten Seitenwechsel bleibt Browser-limitiert (nach Nav 1x Play, springt an gemerkte Position).
v00: 3 Real-Use-Findings (Holm) — #53 wiedereroeffnet
Nach dem PASS-CDP-Test hat Holm im echten Gebrauch drei Punkte gefunden, die mein Ein-Folge-Test nicht abdeckte:
#2 Cross-Episode-Position (echter Bug, CDP-reproduziert)
Folge A spielen (curT=2.2) -> zur Seite von Folge B navigieren -> "folge abspielen" auf B ->
src=B.mp3abercurT=2.2(Position von A auf Datei B).Root Cause: Der Rehydrate-Block registriert beim Laden
audio.addEventListener('loadedmetadata', once)mitstate.currentTimedes ALTEN States (A) per Closure.loadEpisode()wechselt danachsrcauf B, entfernt diesen pending Handler aber nicht -> er feuert auf B'sloadedmetadataund setzt A's Position. sessionStorage sagt B/0, echtesaudio.currentTimeist 2.2.Fix-Richtung:
loadEpisode()muss bei Episodenwechsel den pending currentTime-Restore-Handler neutralisieren (bzw. der Restore liest currentTime frisch aus readState() statt aus Closure-Variable + prueft src). Danach curT=0 bei neuer Folge.#3 Seekbar-Hit-Area (UX/CSS)
.zt-sticky-player__seek { height: 3px }(style.css:1198) -> Klickflaeche nur 3px hoch, "pixelgenau zielen". Fix: groessere Hit-Area (hoehe/vertikales padding/groesserer Thumb, klickbarer Track ~16-20px).#1 Seitenwechsel stockt (Architektur-Grenze, kein Bug)
Bei jedem echten Full-Page-Load wird
<audio>zerstoert + neu geladen, Autoplay ohne neue Geste blockiert -> Wiedergabe stockt. Inhaerent bei Ansatz A (statische Multi-Page + sessionStorage). Nahtloses Weiterspielen nur mit Ansatz B (SPA/fetch-Navigation).Architektur-Entscheidung Holm ausstehend: A sauber machen (#2+#3 fixen, Stocken bleibt) vs. B (SPA-Umbau, nahtlos).
v00 #53 — geschlossen (Ziel via #58 erreicht)
#53 (Player-Persistenz ueber Seitenwechsel) ist mit dem SPA-Umbau (#58, Ansatz B) vollstaendig erfuellt: der Sticky-Player ueberlebt jede interne Navigation, Audio laeuft nahtlos weiter — CDP-bewiesen (Marker ueberlebt alle Links, kein Reload).
Verlauf: Ansatz A (sessionStorage-Rehydrate) war Zwischenstand und konnte nahtloses Weiterspielen prinzipiell nicht (Full-Page-Reload toetet
<audio>). Ansatz B (SPA) ist die finale Loesung und erschlaegt zugleich den Cross-Episode-Positionsbug. Player klebt durchgaengig unten, Seekbar greifbar (#59), Live/Chat intakt.Geschlossen durch v00.