Files
MeBox/internal/service/emby_item_payload_empty_ids_test.go
truewhile 8136e6ecb1 fix(emby): 电影条目不下发空的 SeasonId/SeriesId,修复小幻影视起播失败
小幻影视(RodelPlayer)2.2610 会把电影条目的 SeasonId 空串原样传进自己的
播放准备逻辑并抛 ArgumentException(Parameter 'seasonId'),客户端日志里
表现为 failedStage=PreparationFailed、playback generation null,也就是
点播放立刻「播放失败」;剧集条目的 SeasonId 非空,所以同一台设备放剧集正常。

Emby/Jellyfin 对「不适用」的字段是直接不下发(JSON 里没有该键)而不是下发空串。
这里对非剧集条目省略 SeasonId/SeriesId/SeasonName/SeriesName(顺带去掉电影被
标成「特别篇」的 SeasonName),剧集则保留真实季/剧信息、只清空值。

同时回退上一版针对 115 直链 UA 绑定的同源一跳改动:客户端日志显示该客户端的
播放器层与 API 层 UA 是一致的(剧集走服务端换链后的一跳直连可以正常解码播放),
不需要为此牺牲起播速度。
2026-09-28 22:51:40 +08:00

93 lines
3.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"testing"
"github.com/truewhile/MeBox/internal/model"
)
// Emby/Jellyfin 对「不适用」的字段不下发(JSON 里没有该键),而不是下发空串。
// 小幻影视(RodelPlayer)2.2610 会把电影条目的 "SeasonId": "" 原样传进播放准备
// 逻辑并抛 ArgumentException(Parameter 'seasonId'),导致整个起播失败。
func TestEmbyMovieItemOmitsEmptySeasonAndSeriesFields(t *testing.T) {
svc := newTestEmbyService(t)
lib := model.Library{Name: "小姐姐", Path: `/media/小姐姐`, Type: "movie", Enabled: true}
if err := svc.repo.Library.Create(t.Context(), &lib); err != nil {
t.Fatalf("create library: %v", err)
}
movie := model.Media{
Base: model.Base{ID: "movie-strm"},
LibraryID: lib.ID,
Title: "SNIS-919",
Path: `/media/小姐姐/三上悠亚/SNIS-919/SNIS-919.strm`,
Container: "strm",
STRMURL: "/api/strm/play/cloud115/video.mkv?acct=a1&pickcode=pc1",
}
if err := svc.repo.DB.Create(&movie).Error; err != nil {
t.Fatalf("create media: %v", err)
}
item := svc.itemPayload(t.Context(), &movie, false, 0, false)
if item["Type"] != "Movie" {
t.Fatalf("Type = %#v, want Movie", item["Type"])
}
for _, key := range []string{"SeasonId", "SeriesId", "SeasonName", "SeriesName"} {
if value, ok := item[key]; ok {
t.Fatalf("%s must be omitted for a movie, got %#v", key, value)
}
}
}
// 剧集的季/剧归属是真实存在的,不能被一起删掉。
func TestEmbyEpisodeItemKeepsSeasonAndSeriesFields(t *testing.T) {
svc := newTestEmbyService(t)
lib := model.Library{Name: "剧集", Path: `/media/tv`, Type: "tv", Enabled: true}
if err := svc.repo.Library.Create(t.Context(), &lib); err != nil {
t.Fatalf("create library: %v", err)
}
episode := model.Media{
Base: model.Base{ID: "ep-1"},
LibraryID: lib.ID,
Title: "命运石之门",
Path: `/media/tv/命运石之门/Season 01/命运石之门 - S01E01.mkv`,
SeasonNum: 1,
EpisodeNum: 1,
}
if err := svc.repo.DB.Create(&episode).Error; err != nil {
t.Fatalf("create media: %v", err)
}
item := svc.itemPayload(t.Context(), &episode, false, 0, false)
if item["Type"] != "Episode" {
t.Fatalf("Type = %#v, want Episode", item["Type"])
}
for _, key := range []string{"SeasonId", "SeriesId", "SeasonName", "SeriesName"} {
value, _ := item[key].(string)
if value == "" {
t.Fatalf("%s = %#v, want a non-empty value for an episode", key, item[key])
}
}
}
func TestDropEmptyStringFields(t *testing.T) {
item := map[string]any{
"SeasonId": "",
"SeriesId": " ",
"SeriesName": "命运石之门",
"IndexNumber": 0,
}
dropEmptyStringFields(item, "SeasonId", "SeriesId", "SeriesName")
if _, ok := item["SeasonId"]; ok {
t.Fatalf("empty SeasonId must be removed")
}
if _, ok := item["SeriesId"]; ok {
t.Fatalf("whitespace-only SeriesId must be removed")
}
if item["SeriesName"] != "命运石之门" {
t.Fatalf("non-empty SeriesName must be kept, got %#v", item["SeriesName"])
}
if item["IndexNumber"] != 0 {
t.Fatalf("non-string fields must be left alone, got %#v", item["IndexNumber"])
}
}