App v0.2.27 and gateway 0.1.23

Skip Intro from Emby's own chapter markers, trickplay seek previews from
BIF files, a server-composed home hero ranked on Radarr/Sonarr dates and
review scores, and My Alerts as its own page behind the user picker.

Related titles now degrade at every step instead of returning empty, and
the "+" is back on Manage users so a second viewer can be added from the
launcher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ponzischeme89
2026-08-07 10:44:17 +12:00
co-authored by Claude Opus 5
parent 4a4df7a73c
commit 80c304d86b
62 changed files with 6095 additions and 255 deletions
+28 -2
View File
@@ -67,10 +67,12 @@ func (s *Server) handleHome(w http.ResponseWriter, r *http.Request, sess store.S
limit := queryInt(r, "limit", 24, 100)
sonarrSchedule := s.sonarr != nil && supportsSonarrSchedule(r)
radarrSchedule := s.radarr != nil && supportsRadarrSchedule(r)
hero := supportsHomeHero(r)
key := cache.UserKey(
sess.EmbyUserID,
"home:v3:"+itoa(limit)+":s"+strconv.FormatBool(sonarrSchedule)+
":r"+strconv.FormatBool(radarrSchedule)+":d"+sess.DeviceID,
"home:v4:"+itoa(limit)+":s"+strconv.FormatBool(sonarrSchedule)+
":r"+strconv.FormatBool(radarrSchedule)+":h"+strconv.FormatBool(hero)+
":d"+sess.DeviceID,
)
if raw, err := s.cache.Get(ctx, key); err == nil {
@@ -282,6 +284,16 @@ func (s *Server) handleHome(w http.ResponseWriter, r *http.Request, sess store.S
// the launcher pays one indexed read rather than a request per poster, and a card
// shows its scores as it is drawn instead of when focus reaches it.
s.decorateHomeRatings(ctx, &out)
// The hero is composed last, from the finished rows, because that is the only point
// at which the ratings it ranks by are already attached. It is prepended rather than
// inserted: the television consumes this row instead of drawing it, so its position
// among the shelves means nothing, and being first is what lets an older reader that
// does draw it put it somewhere sensible.
if hero {
if row := s.heroRow(ctx, out.Rows, time.Now()); row != nil {
out.Rows = append([]recommend.Row{*row}, out.Rows...)
}
}
body, err := json.Marshal(out)
if err != nil {
@@ -445,6 +457,20 @@ func supportsRadarrSchedule(r *http.Request) bool {
return version != "" && appupdate.CompareVersions(version, "0.1.79") >= 0
}
// The server-composed hero ships in 0.2.27. Gating it matters more than gating a shelf:
// a television that predates it has no idea the "hero" kind is meant to be consumed
// rather than drawn, so it renders the featured cards a second time as a "Featured" row
// of posters beneath the hero it picked for itself.
//
// Note that 0.2.27 is also the version the feature was *added to* rather than a version
// after it, so any 0.2.27 build already in the field is one of the televisions this is
// meant to exclude. That is a deliberate call by the operator; if it bites, moving this
// floor to the next version is the fix, not a client change.
func supportsHomeHero(r *http.Request) bool {
version := clientVersion(r)
return version != "" && appupdate.CompareVersions(version, "0.2.27") >= 0
}
// handleScreensaver serves the backdrop pool. The pool is cached and shuffled per
// request, so the Dream still looks random without re-querying Emby every few seconds.
func (s *Server) handleScreensaver(w http.ResponseWriter, r *http.Request, sess store.Session) {