feat(podcasts): chain playback + Continue section — sequence mode in the web player (phase 2 core)
D7 chain-by-default, delivered with minimal player surgery: - ReaderMetadata gains PodcastSequence: the neighboring DOWNLOADED episodes in the caller's effective order (D5 override applied, feed-native fallback; computed in the reader route for source='podcast_episode' items — RLS already scoped the reads; gaps never enter the chain, invariant 6). ?single=1 escapes chaining (the 'play single episode' affordance). - Audio reader renders the sequence into body[data-json] (AudioReaderConfigJSON — books produce the identical shape minus the sequence key, so the reader path is unchanged for them) and an 'Ep N/M' indicator + prev/next episode controls in the header. - audio.ts: the last-track 'ended' handler chains into the next episode's reader page (final progress push precedes navigation); back button exits episodes to their feed page. Books are untouched — no sequence context, zero behavior change. Day-one chaining is page-navigation based (progress persists; position restores). Continue aggregation (2.5): GetPodcastContinueForUser (in-progress episodes, most-recent-first, ≥95% excluded) feeds a Continue section on /podcasts — sorted most recently listened, cards show 'Ep N · title' — ahead of All Feeds. Cards prefer the continue line over the description.
This commit is contained in:
1 parent
2235b00c59
commit
28420d56ed
14 files changed
+740
-174
No files matched your search
@@ -107,6 +107,18 @@ func buildPodcastsPageData(c *echo.Context, cfg *Config) (*templates.PodcastsPag
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
// Continue aggregation (plan 2.5): first in-progress episode per
|
||||
// feed (the query is already most-recent-first).
|
||||
continueByFeed := make(map[string]*database.GetPodcastContinueForUserRow)
|
||||
if continueRows, err := cfg.Queries.GetPodcastContinueForUser(ctx, user.ID); err == nil {
|
||||
for i := range continueRows {
|
||||
row := &continueRows[i]
|
||||
key := uuid.UUID(row.SourceFeedID.Bytes).String()
|
||||
if _, ok := continueByFeed[key]; !ok {
|
||||
continueByFeed[key] = row
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range feeds {
|
||||
row := &feeds[i]
|
||||
total, _ := cfg.Queries.CountPodcastEpisodesForFeed(ctx, row.ID)
|
||||
@@ -137,6 +149,14 @@ func buildPodcastsPageData(c *echo.Context, cfg *Config) (*templates.PodcastsPag
|
||||
AutoDownloadCount: int(row.AutoDownloadCount.Int32),
|
||||
Subscribed: true,
|
||||
})
|
||||
if cont := continueByFeed[uuid.UUID(row.ID.Bytes).String()]; cont != nil {
|
||||
card := &data.Feeds[len(data.Feeds)-1]
|
||||
card.HasContinue = true
|
||||
card.ContinueTitle = cont.Title
|
||||
card.ContinueEpisode = int(cont.FeedPosition.Int32)
|
||||
card.ContinuePercent = cont.Percentage.Float64
|
||||
card.ContinueLastRead = cont.LastReadAt.Time
|
||||
}
|
||||
}
|
||||
|
||||
// Facet values from the unfiltered subscription list (D18).
|
||||
@@ -178,6 +198,7 @@ func buildPodcastsPageData(c *echo.Context, cfg *Config) (*templates.PodcastsPag
|
||||
}
|
||||
data.Feeds = filtered
|
||||
data.Query = q
|
||||
data.ContinueFeeds = templates.PodcastContinueFeeds(data.Feeds)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user