mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-14 14:18:42 +01:00
* feat(jellyfin): add LyricDto and lyrics mapper
* feat(jellyfin): advertise Lyric media stream for embedded lyrics
* feat(jellyfin): implement GET /Audio/{itemId}/Lyrics
* feat(jellyfin): advertise pipeline-resolved lyrics in PlaybackInfo
* feat(jellyfin): advertise server version 10.9.11 for client lyrics gates
* test(jellyfin): e2e coverage for lyrics endpoint and advertising
Seeds "Stairway To Heaven" with an embedded LRC lyric tag (lyrics:eng)
and covers PlaybackInfo's Lyric MediaStream, GET /Audio/{id}/Lyrics,
and the HasLyrics badge end to end.
Fixes a bug the new seed exposed: HasLyrics and the Lyric MediaStream
gate compared mf.Lyrics against "", but the persistence layer never
stores an empty string post-scan (it normalizes to the JSON sentinel
"[]"), so every track was reporting HasLyrics=true. Both call sites
now parse the column via StructuredLyrics()/LyricList.Main() instead.
* fix(jellyfin): cheap sentinel check for embedded lyrics advertising
* chore(jellyfin): trim over-budget comments in lyrics code
* test(jellyfin): cover lyrics pipeline error and nil-start cue skip
* docs(jellyfin): document lyrics support and follow-ups in README
* refactor(jellyfin): promote embedded-lyrics sentinel check to MediaFile
The "[]" no-lyrics sentinel is persistence-layer knowledge; expose it as
model.MediaFile.HasEmbeddedLyrics() instead of a dto-local helper. Also
dedupe the test lyrics-cache construction and pre-size the media stream
slice.
* refactor(jellyfin): consolidate tick conversions around one constant
ticksPerMillis is now the single source of the 100ns-tick unit; the
scrobble handlers' three inline /10_000 divisions become
dto.MillisFromTicks.
* fix(jellyfin): align lyric advertising with the serving predicate
PlaybackInfo advertised on any non-empty LyricList while the endpoint
404s when the main lyric has no lines; both now share servableLyric.
Handler tests also send hex-encoded ids to match real traffic.
* chore(jellyfin): drop unneeded lyrics package alias in e2e suite
56 lines
2 KiB
Go
56 lines
2 KiB
Go
package jellyfin
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/navidrome/navidrome/tests"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
// Real Jellyfin servers route path segments case-insensitively, but chi's default matching is
|
|
// case-sensitive. Jellyfin wires up server.CaseInsensitivePaths (see server/case_insensitive_routes.go
|
|
// for the unit-level tests of that helper) to work around this. These tests are an end-to-end proof
|
|
// that requests using non-canonical casing are still routed correctly, both when the router is used
|
|
// directly and when mounted under a parent (as it is in production via server.MountRouter).
|
|
var _ = Describe("Case-insensitive routing", func() {
|
|
var api *Router
|
|
|
|
BeforeEach(func() {
|
|
api = New(&tests.MockDataStore{}, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
|
})
|
|
|
|
It("serves a fully lowercase path directly", func() {
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("GET", "/system/info/public", nil)
|
|
api.ServeHTTP(w, r)
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
})
|
|
|
|
It("serves a mixed/weird-case path directly", func() {
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("GET", "/SYSTEM/Info/PUBLIC", nil)
|
|
api.ServeHTTP(w, r)
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
})
|
|
|
|
It("serves a lowercase login path directly", func() {
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("POST", "/users/authenticatebyname", nil)
|
|
api.ServeHTTP(w, r)
|
|
// MockDataStore has no users, so authentication itself may fail downstream, but the
|
|
// route must be found (not a 404) to prove case-insensitive matching worked.
|
|
Expect(w.Code).ToNot(Equal(http.StatusNotFound))
|
|
})
|
|
|
|
It("serves a lowercase path when mounted under a parent router, replicating production", func() {
|
|
parent := chi.NewRouter()
|
|
parent.Mount("/jellyfin", api)
|
|
w := httptest.NewRecorder()
|
|
r := httptest.NewRequest("GET", "/jellyfin/system/info/public", nil)
|
|
parent.ServeHTTP(w, r)
|
|
Expect(w.Code).To(Equal(http.StatusOK))
|
|
})
|
|
})
|