mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
fix: honor STRM playback mode for cloud clients
This commit is contained in:
@@ -573,6 +573,7 @@ func embyItemByIDHandler(svc *service.Container) gin.HandlerFunc {
|
||||
embyError(c, http.StatusNotFound, "item not found")
|
||||
return
|
||||
}
|
||||
embyAttachRequestTokenToPlaybackInfo(c, out)
|
||||
c.JSON(http.StatusOK, out)
|
||||
}
|
||||
}
|
||||
@@ -1064,6 +1065,18 @@ func embyEmptyItemsHandler(_ *service.Container) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func embyServerDomainsHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, []any{})
|
||||
}
|
||||
}
|
||||
|
||||
func embyDanmuRawHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "text/plain; charset=utf-8", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func embyBrandingConfigHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@@ -1119,6 +1132,10 @@ func registerEmbyRoutes(r *gin.Engine, jwtSecret string, svc *service.Container)
|
||||
for _, path := range []string{"/System/Endpoint", "/system/endpoint"} {
|
||||
grp.GET(path, embySystemEndpointHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/System/Ext/ServerDomains", "/system/ext/serverdomains"} {
|
||||
grp.GET(path, embyServerDomainsHandler(svc))
|
||||
grp.HEAD(path, embyServerDomainsHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/System/Configuration/Public", "/system/configuration/public"} {
|
||||
grp.GET(path, embyPublicServerConfigurationHandler(svc))
|
||||
grp.HEAD(path, embyPublicServerConfigurationHandler(svc))
|
||||
@@ -1193,6 +1210,8 @@ func registerEmbyRoutes(r *gin.Engine, jwtSecret string, svc *service.Container)
|
||||
auth.GET("/Shows/NextUp", embyEmptyItemsHandler(svc))
|
||||
auth.GET("/Users/:userId/Shows/NextUp", embyEmptyItemsHandler(svc))
|
||||
auth.GET("/MediaSegments/:id", embyEmptyItemsHandler(svc))
|
||||
auth.GET("/Items/:id/Similar", embyEmptyItemsHandler(svc))
|
||||
auth.GET("/api/danmu/:id/raw", embyDanmuRawHandler(svc))
|
||||
|
||||
auth.GET("/Items/:id/PlaybackInfo", embyPlaybackInfoHandler(svc))
|
||||
auth.POST("/Items/:id/PlaybackInfo", embyPlaybackInfoHandler(svc))
|
||||
@@ -1257,6 +1276,7 @@ func registerLowercaseEmbyAuthRoutes(auth *gin.RouterGroup, svc *service.Contain
|
||||
auth.GET("/shows/nextup", embyEmptyItemsHandler(svc))
|
||||
auth.GET("/users/:userId/shows/nextup", embyEmptyItemsHandler(svc))
|
||||
auth.GET("/mediasegments/:id", embyEmptyItemsHandler(svc))
|
||||
auth.GET("/items/:id/similar", embyEmptyItemsHandler(svc))
|
||||
|
||||
auth.GET("/items/:id/playbackinfo", embyPlaybackInfoHandler(svc))
|
||||
auth.POST("/items/:id/playbackinfo", embyPlaybackInfoHandler(svc))
|
||||
|
||||
@@ -179,6 +179,56 @@ func TestEmbyPublicSystemInfoLooksLikeModernEmbyServer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbyMobileCompatibilityRoutesAvoidPlaybackBlocking404s(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open db: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&model.User{}); err != nil {
|
||||
t.Fatalf("migrate: %v", err)
|
||||
}
|
||||
if err := db.Create(&model.User{
|
||||
Base: model.Base{ID: "user-1"},
|
||||
Username: "viewer",
|
||||
PasswordHash: "hash",
|
||||
Role: "admin",
|
||||
IsActive: true,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create user: %v", err)
|
||||
}
|
||||
cfg := &config.Config{}
|
||||
cfg.Secrets.JWTSecret = "test-secret"
|
||||
repos := repository.New(db)
|
||||
router := gin.New()
|
||||
registerEmbyRoutes(router, cfg.Secrets.JWTSecret, &service.Container{
|
||||
Repo: repos,
|
||||
Emby: service.NewEmbyService(cfg, zap.NewNop(), repos),
|
||||
})
|
||||
|
||||
token := signedTestToken(t, cfg.Secrets.JWTSecret)
|
||||
tests := []struct {
|
||||
path string
|
||||
auth bool
|
||||
wantCode int
|
||||
}{
|
||||
{path: "/emby/System/Ext/ServerDomains", wantCode: http.StatusOK},
|
||||
{path: "/emby/Items/msgo-series-demo/Similar", auth: true, wantCode: http.StatusOK},
|
||||
{path: "/emby/api/danmu/media-demo/raw", auth: true, wantCode: http.StatusOK},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
req := httptest.NewRequest(http.MethodGet, tc.path, nil)
|
||||
if tc.auth {
|
||||
req.Header.Set("X-Emby-Token", token)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
if w.Code != tc.wantCode {
|
||||
t.Fatalf("%s status = %d body=%s", tc.path, w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbySenPlayerDiscoveryRoutesReturnProtocolResponses(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
@@ -645,14 +695,14 @@ func TestEmbyPlaybackInfoTokenizesCloudPath(t *testing.T) {
|
||||
}
|
||||
source := body["MediaSources"].([]any)[0].(map[string]any)
|
||||
pathURL, _ := source["Path"].(string)
|
||||
if !strings.HasPrefix(pathURL, "/Videos/cloud-1/stream") || !strings.Contains(pathURL, "api_key=") {
|
||||
if !strings.HasPrefix(pathURL, "/api/stream/cloud-1") || !strings.Contains(pathURL, "api_key=") {
|
||||
t.Fatalf("cloud Path should be tokenized stream URL, got %#v", source)
|
||||
}
|
||||
if strings.Contains(pathURL, "/api/cloud/play/") {
|
||||
t.Fatalf("cloud Path should not expose naked cloud play URL: %#v", source)
|
||||
}
|
||||
directURL, _ := source["DirectStreamUrl"].(string)
|
||||
if !strings.HasPrefix(directURL, "/Videos/cloud-1/stream.") || !strings.Contains(directURL, "api_key=") {
|
||||
if !strings.HasPrefix(directURL, "/api/stream/cloud-1") || !strings.Contains(directURL, "api_key=") {
|
||||
t.Fatalf("DirectStreamUrl should stay tokenized: %#v", source)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user