mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 20:26:36 +08:00
feat: 网页端完整浏览/播放远程 Emby 挂载库
- 后端新增远程库→本地结构映射(emby_remote_web.go):Library/Media/SeriesCard 与本地完全同构,前端无感知 - /api/libraries(含 with_preview)、/api/libraries/:id、/api/libraries/:id/media、 /api/libraries/:id/series、/api/libraries/:id/series/episodes、/api/media/:id、 /api/media/:id/episodes 全部支持远程伪装 ID 透传映射 - /api/stream/:id 对远程条目 302 直连远程 Emby 原地址(播放不经过本机) - 库内容按 CollectionType 过滤(电影库取 Movie、剧集库取 Series), RecursiveItemCount 作为系列集数;修复伪装 ID 双重编码导致的图片/详情 500 - SPA fallback 放行 /library/embyremote~ 前缀(不再被当作 Emby API 路由 404) - 前端:远程库/条目标记 is_remote_emby,隐藏扫描/刮削/编辑/NFO/回收站/ HLS 转码等不适用操作;后台管理面板过滤远程库
This commit is contained in:
@@ -65,7 +65,49 @@ func listSeasonsHandler(svc *service.Container) gin.HandlerFunc {
|
||||
func listLibrarySeriesHandler(svc *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
libID := c.Param("id")
|
||||
if lib, err := svc.Repo.Library.FindByID(c.Request.Context(), libID); err == nil && lib != nil {
|
||||
ctx := c.Request.Context()
|
||||
// 远程剧集库:远程 Series 映射为系列卡片。
|
||||
if svc.EmbyRemote != nil && service.IsEmbyRemoteID(libID) {
|
||||
acctID, remoteID, _ := service.DecodeEmbyRemoteID(libID)
|
||||
acct := svc.EmbyRemote.AccountByID(ctx, acctID)
|
||||
if acct == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
cards, err := svc.EmbyRemote.RemoteSeriesCards(ctx, acct, remoteID)
|
||||
if err != nil {
|
||||
writeInternalOrCanceled(c, err)
|
||||
return
|
||||
}
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("page_size", "500"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size <= 0 || size > 1000 {
|
||||
size = 500
|
||||
}
|
||||
start := (page - 1) * size
|
||||
if start > len(cards) {
|
||||
start = len(cards)
|
||||
}
|
||||
end := start + size
|
||||
if end > len(cards) {
|
||||
end = len(cards)
|
||||
}
|
||||
pageItems := cards[start:end]
|
||||
if pageItems == nil {
|
||||
pageItems = []service.SeriesCard{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"items": pageItems,
|
||||
"total": len(cards),
|
||||
"page": page,
|
||||
"page_size": size,
|
||||
})
|
||||
return
|
||||
}
|
||||
if lib, err := svc.Repo.Library.FindByID(ctx, libID); err == nil && lib != nil {
|
||||
if !service.LibraryVisibleForUser(c.Request.Context(), svc.Repo, *lib, mediaVisibilityForRequest(c, svc)) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
@@ -114,7 +156,27 @@ func listLibrarySeriesEpisodesHandler(svc *service.Container) gin.HandlerFunc {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "key is required"})
|
||||
return
|
||||
}
|
||||
if lib, err := svc.Repo.Library.FindByID(c.Request.Context(), libID); err == nil && lib != nil {
|
||||
ctx := c.Request.Context()
|
||||
// 远程系列 key(伪装系列 ID):转发远程该系列全部剧集。
|
||||
if svc.EmbyRemote != nil && service.IsEmbyRemoteID(key) {
|
||||
acctID, remoteSeriesID, _ := service.DecodeEmbyRemoteID(key)
|
||||
acct := svc.EmbyRemote.AccountByID(ctx, acctID)
|
||||
if acct == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
items, err := svc.EmbyRemote.RemoteEpisodes(ctx, acct, remoteSeriesID)
|
||||
if err != nil {
|
||||
writeInternalOrCanceled(c, err)
|
||||
return
|
||||
}
|
||||
if items == nil {
|
||||
items = []model.Media{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"items": items, "total": len(items)})
|
||||
return
|
||||
}
|
||||
if lib, err := svc.Repo.Library.FindByID(ctx, libID); err == nil && lib != nil {
|
||||
if !service.LibraryVisibleForUser(c.Request.Context(), svc.Repo, *lib, mediaVisibilityForRequest(c, svc)) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
@@ -136,7 +198,27 @@ func listMediaEpisodesHandler(svc *service.Container) gin.HandlerFunc {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
|
||||
return
|
||||
}
|
||||
items, err := svc.Media.ListMediaEpisodes(c.Request.Context(), id, mediaVisibilityForRequest(c, svc))
|
||||
ctx := c.Request.Context()
|
||||
// 远程条目:单集→同系列集列表;系列/季/文件夹→子集;电影→自身单条。
|
||||
if svc.EmbyRemote != nil && service.IsEmbyRemoteID(id) {
|
||||
acctID, remoteID, _ := service.DecodeEmbyRemoteID(id)
|
||||
acct := svc.EmbyRemote.AccountByID(ctx, acctID)
|
||||
if acct == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
items, err := svc.EmbyRemote.RemoteEpisodes(ctx, acct, remoteID)
|
||||
if err != nil {
|
||||
writeInternalOrCanceled(c, err)
|
||||
return
|
||||
}
|
||||
if items == nil {
|
||||
items = []model.Media{}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"items": items, "total": len(items)})
|
||||
return
|
||||
}
|
||||
items, err := svc.Media.ListMediaEpisodes(ctx, id, mediaVisibilityForRequest(c, svc))
|
||||
if err != nil {
|
||||
writeInternalOrCanceled(c, err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user