mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-03 08:57:53 +01:00
feat(podcast): add podcast feature with UX improvements - #5420
Backend - Add podcast data model (PodcastChannel, PodcastEpisode) with migrations - Implement Subsonic API endpoints: getPodcasts, getNewestPodcasts, createPodcastChannel, refreshPodcasts, deletePodcastChannel, deletePodcastEpisode, downloadPodcastEpisode, getPodcastEpisode - Add native REST API endpoints: GET/DELETE /api/podcast, GET /api/podcast/preview (feed info without creating channel) - Inject events.Broker into podcast service for SSE support - Emit PodcastEpisodeProgress SSE events during download (every 512 KB) and on completion/error with status field - Use HTTP Content-Length as fallback when RSS feed omits enclosure size - Write ID3 tags (title, album, genre=Podcast) to downloaded files via ffmpeg so the library scanner reads correct metadata - Set MediaFile fields (Title, Album, AlbumID=channelId, AlbumArtist, Genre) on episode registration - Add duplicate URL check in AddChannel - Add ExistsByURL to PodcastChannelRepository Frontend - Podcast list - Add grid/list view toggle (Redux podcastViewReducer) matching album list - New PodcastGridView component with responsive column count (2-6 cols) - Cover image 100px in table view - Remove Feed URL column; add inline copy-to-clipboard button Frontend - Podcast show (episode list) - Real-time download progress (%) in Status column via SSE, no polling - Spinner only before first SSE event; N% once data arrives - Size column removed; Downloading badge replaced with progress - Completed episodes play on row click; separate play button removed - Play / Shuffle / Play Next / Add to Queue buttons above episode list (only shown when completed episodes exist) - On download completion, reload episodes to obtain streamId for immediate playback without page refresh - Clicking album name in AudioTitle navigates to podcast channel page Frontend - Podcast create - Full-width URL input with Fetch Feed Info button and Enter key support - Preview card (cover image, title, episode count, description) before committing channel creation - Add Channel button appears only after preview; shows already-registered message if channel URL exists Frontend - Playlist - Album link navigates to podcast channel page for podcast tracks (identified by genre=Podcast) - Artist column shows '-' for podcast tracks with empty artist field Closes #5420 Signed-off-by: ji-ho lee <search5@gmail.com>
This commit is contained in:
parent
16567f147b
commit
775747264b
50 changed files with 3769 additions and 36 deletions
|
|
@ -75,6 +75,7 @@ func (api *Router) routes() http.Handler {
|
|||
api.R(r, "/player", model.Player{}, true)
|
||||
api.R(r, "/transcoding", model.Transcoding{}, conf.Server.EnableTranscodingConfig)
|
||||
api.addRadioRoute(r)
|
||||
api.addPodcastRoute(r)
|
||||
api.R(r, "/tag", model.Tag{}, false)
|
||||
api.R(r, "/scrobble", model.Scrobble{}, false)
|
||||
if conf.Server.EnableSharing {
|
||||
|
|
|
|||
45
server/nativeapi/podcasts.go
Normal file
45
server/nativeapi/podcasts.go
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package nativeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/deluan/rest"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/core/podcasts"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
)
|
||||
|
||||
func (api *Router) addPodcastRoute(r chi.Router) {
|
||||
constructor := func(ctx context.Context) rest.Repository {
|
||||
return api.ds.Resource(ctx, model.PodcastChannel{})
|
||||
}
|
||||
r.Route("/podcast", func(r chi.Router) {
|
||||
r.Get("/", rest.GetAll(constructor))
|
||||
r.Get("/preview", api.podcastPreview)
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
r.Use(server.URLParamsMiddleware)
|
||||
r.Get("/", rest.Get(constructor))
|
||||
r.Delete("/", rest.Delete(constructor))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (api *Router) podcastPreview(w http.ResponseWriter, r *http.Request) {
|
||||
feedURL := r.URL.Query().Get("url")
|
||||
if feedURL == "" {
|
||||
http.Error(w, "url is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
feed, err := podcasts.ParseFeedPreview(feedURL)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
exists, _ := api.ds.PodcastChannel(r.Context()).ExistsByURL(feedURL)
|
||||
feed.AlreadyExists = exists
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(feed)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue